home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / calendar-multiday-view.xml < prev    next >
Extensible Markup Language  |  2008-03-18  |  145KB  |  3,699 lines

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--
  3.    - ***** BEGIN LICENSE BLOCK *****
  4.    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5.    -
  6.    - The contents of this file are subject to the Mozilla Public License Version
  7.    - 1.1 (the "License"); you may not use this file except in compliance with
  8.    - the License. You may obtain a copy of the License at
  9.    - http://www.mozilla.org/MPL/
  10.    -
  11.    - Software distributed under the License is distributed on an "AS IS" basis,
  12.    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13.    - for the specific language governing rights and limitations under the
  14.    - License.
  15.    -
  16.    - The Original Code is calendar views.
  17.    -
  18.    - The Initial Developer of the Original Code is
  19.    -   Oracle Corporation
  20.    - Portions created by the Initial Developer are Copyright (C) 2005
  21.    - the Initial Developer. All Rights Reserved.
  22.    -
  23.    - Contributor(s):
  24.    -   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  25.    -   Thomas Benisch <thomas.benisch@sun.com>
  26.    -   Dan Mosedale <dan.mosedale@oracle.com>
  27.    -   Michael Buettner <michael.buettner@sun.com>
  28.    -   Philipp Kewisch <mozilla@kewis.ch>
  29.    -   Markus Adrario <MarkusAdrario@web.de>
  30.    -
  31.    - Alternatively, the contents of this file may be used under the terms of
  32.    - either the GNU General Public License Version 2 or later (the "GPL"), or
  33.    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  34.    - in which case the provisions of the GPL or the LGPL are applicable instead
  35.    - of those above. If you wish to allow use of your version of this file only
  36.    - under the terms of either the GPL or the LGPL, and not to allow others to
  37.    - use your version of this file under the terms of the MPL, indicate your
  38.    - decision by deleting the provisions above and replace them with the notice
  39.    - and other provisions required by the GPL or the LGPL. If you do not delete
  40.    - the provisions above, a recipient may use your version of this file under
  41.    - the terms of any one of the MPL, the GPL or the LGPL.
  42.    -
  43.    - ***** END LICENSE BLOCK *****
  44. -->
  45.  
  46. <!-- Note that this file depends on helper functions in calUtils.js-->
  47.  
  48. <bindings id="calendar-multiday-view-bindings"
  49.   xmlns="http://www.mozilla.org/xbl"
  50.   xmlns:html="http://www.w3.org/1999/xhtml"
  51.   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  52.   xmlns:xbl="http://www.mozilla.org/xbl">
  53.  
  54.   <!--
  55.      - This is the time bar that displays time divisions to the side
  56.      - or top of a multiday view.
  57.     -->
  58.   <binding id="calendar-time-bar">
  59.     <content>
  60.       <xul:box xbl:inherits="orient,width,height" flex="1" anonid="topbox">
  61.       </xul:box>
  62.     </content>
  63.  
  64.     <implementation>
  65.       <field name="mPixPerMin">0.6</field>
  66.       <field name="mStartMin">0*60</field>
  67.       <field name="mEndMin">24*60</field>
  68.       <field name="mDayStartHour">0</field>
  69.       <field name="mDayEndHour">24</field>
  70.  
  71.       <constructor>
  72.         this.relayout();
  73.       </constructor>
  74.  
  75.       <method name="setDayStartEndHours">
  76.         <parameter name="aDayStartHour"/>
  77.         <parameter name="aDayEndHour"/>
  78.         <body><![CDATA[
  79.           if (aDayStartHour * 60 < this.mStartMin ||
  80.               aDayStartHour > aDayEndHour ||
  81.               aDayEndHour * 60 > this.mEndMin) {
  82.               throw Components.results.NS_ERROR_INVALID_ARG;
  83.           }
  84.           if (this.mDayStartHour != aDayStartHour ||
  85.               this.mDayEndHour != aDayEndHour) {
  86.               this.mDayEndHour = aDayEndHour;
  87.               this.mDayStartHour = aDayStartHour;
  88.  
  89.               var topbox = document.getAnonymousElementByAttribute(this, "anonid", "topbox");
  90.               if (topbox.childNodes.length) {
  91.                   // This only needs to be done if the initial relayout has
  92.                   // already happened, otherwise it will be done then.
  93.                   for (var hour = this.mStartMin / 60; hour < this.mEndMin / 60; hour++) {
  94.                       if (hour < this.mDayStartHour || hour > this.mDayEndHour) {
  95.                           topbox.childNodes[hour].setAttribute("off-time", "true");
  96.                       } else {
  97.                           topbox.childNodes[hour].removeAttribute("off-time");
  98.                       }
  99.                   }
  100.               }
  101.           }
  102.         ]]></body>
  103.       </method>
  104.  
  105.       <method name="setAttribute">
  106.         <parameter name="aAttr"/>
  107.         <parameter name="aVal"/>
  108.         <body><![CDATA[
  109.           var needsrelayout = false;
  110.           if (aAttr == "orient") {
  111.               if (this.getAttribute("orient") != aVal)
  112.                   needsrelayout = true;
  113.           }
  114.  
  115.           // this should be done using lookupMethod(), see bug 286629
  116.           var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
  117.  
  118.           if (needsrelayout) {
  119.               this.relayout();
  120.           }
  121.  
  122.           return ret;
  123.         ]]></body>
  124.       </method>
  125.  
  126.       <property name="pixelsPerMinute"
  127.         onget="return this.mPixPerMin"
  128.         onset="if (this.mPixPerMin != val) { this.mPixPerMin = val; this.relayout(); } return val;"/>
  129.  
  130.       <method name="relayout">
  131.         <body><![CDATA[
  132.           //dump ("calendar-time-bar: relayout\n");
  133.           function createXULElement(el) {
  134.               return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
  135.           }
  136.  
  137.           var topbox = document.getAnonymousElementByAttribute(this, "anonid", "topbox");
  138.           var orient = topbox.getAttribute("orient");
  139.           var otherorient = "vertical";
  140.           if (!orient) orient = "horizontal";
  141.           if (orient == "vertical") otherorient = "horizontal";
  142.  
  143.           //dump ("calendar-time-bar: orient: " + orient + " other: " + otherorient + "\n");
  144.  
  145.           function makeTimeBox(timestr, size) {
  146.               var box = createXULElement("box");
  147.               box.setAttribute("orient", orient);
  148.  
  149.               if (orient == "horizontal") {
  150.                   box.setAttribute("width", size);
  151.               } else {
  152.                   box.setAttribute("height", size);
  153.               }
  154.  
  155.               var label = createXULElement("label");
  156.               label.setAttribute("class", "calendar-time-bar-label");
  157.               label.setAttribute("value", timestr);
  158.               label.setAttribute("align", "center");
  159.  
  160.               box.appendChild(label);
  161.  
  162.               return box;
  163.           }
  164.  
  165.           while (topbox.lastChild)
  166.               topbox.removeChild(topbox.lastChild);
  167.  
  168.           var formatter = Components.classes["@mozilla.org/intl/scriptabledateformat;1"].
  169.                           getService(Components.interfaces.nsIScriptableDateFormat);
  170.           var timeString;
  171.           var theMin = this.mStartMin;
  172.           var theHour = Math.floor(theMin / 60);
  173.           var durLeft = this.mEndMin - this.mStartMin;
  174.  
  175.           while (durLeft > 0) {
  176.               var dur;
  177.               if (this.mEndMin - theMin < 60) {
  178.                   dur = this.mEndMin - theMin;
  179.               } else {
  180.                   dur = theMin % 60;
  181.               }
  182.               theMin += dur;
  183.               if (dur == 0) dur = 60;
  184.  
  185.               // calculate duration pixel as the difference between
  186.               // start pixel and end pixel to avoid rounding errors.
  187.               var startPix = Math.round(theMin * this.mPixPerMin);
  188.               var endPix   = Math.round((theMin + dur) * this.mPixPerMin);
  189.               var durPix   = endPix - startPix;
  190.               var box;
  191.               if (dur != 60) {
  192.                   box = makeTimeBox("", durPix);
  193.               } else {
  194.                   timeString = formatter.FormatTime("",
  195.                                                     Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
  196.                                                     theHour, 0, 0);
  197.                   box = makeTimeBox(timeString, durPix);
  198.               }
  199.  
  200.               // Set up workweek hours
  201.               if (theHour < this.mDayStartHour || theHour >= this.mDayEndHour) {
  202.                   box.setAttribute("off-time", "true");
  203.               } 
  204.  
  205.               box.setAttribute("class", "calendar-time-bar-box-" + (theHour % 2 == 0 ? "even" : "odd"));
  206.               topbox.appendChild(box);
  207.  
  208.               durLeft -= dur;
  209.               theMin += dur;
  210.               theHour++;
  211.           }
  212.         ]]></body>
  213.       </method>
  214.     </implementation>
  215.   </binding>
  216.  
  217.   <!--
  218.      - A simple gripbar that is displayed at the start and end of an
  219.      - event box.  Needs to handle being dragged and resizing the
  220.      - event, thus changing its start/end time.
  221.     -->
  222.   <binding id="calendar-event-gripbar">
  223.     <content>
  224.       <xul:box anonid="thebox" flex="1">
  225.         <xul:spacer flex="1"/>
  226.         <xul:image xbl:inherits="class"/>
  227.         <xul:spacer flex="1"/>
  228.       </xul:box>
  229.     </content>
  230.  
  231.     <implementation>
  232.       <!-- public -->
  233.       <field name="eventElement">null</field>
  234.  
  235.       <property name="parentorient">
  236.         <getter><![CDATA[
  237.           return this.getAttribute("parentorient");
  238.         ]]></getter>
  239.         <setter><![CDATA[
  240.           this.setAttribute("parentorient", val);
  241.           var thebox = document.getAnonymousElementByAttribute(this, "anonid", "thebox");
  242.           if (val == "vertical")
  243.               thebox.setAttribute("orient", "horizontal");
  244.           else
  245.               thebox.setAttribute("orient", "vertical");
  246.           return val;
  247.         ]]></setter>
  248.       </property>
  249.  
  250.       <!-- private -->
  251.       <field name="mSide">top</field>
  252.  
  253.       <field name="mResizing">false</field>
  254.       <field name="mSizeStartX">0</field>
  255.       <field name="mSizeStartY">0</field>
  256.  
  257.       <constructor><![CDATA[
  258.         if (this.getAttribute("side") == "top")
  259.             this.mSide = "top";
  260.         else if (this.getAttribute("side") == "bottom")
  261.             this.mSide = "bottom";
  262.         this.parentorient = this.getAttribute("parentorient");
  263.       ]]></constructor>
  264.  
  265.     </implementation>
  266.  
  267.     <handlers>
  268.       <handler event="mousedown" button="0"><![CDATA[
  269.         // store the attribute 'whichside' in the event object
  270.         // but *don't* call stopPropagation(). as soon as the
  271.         // enclosing event box will receive the event it will
  272.         // make use of this information in order to invoke the
  273.         // appropriate action.
  274.         event.whichside = this.getAttribute("whichside");
  275.       ]]></handler>
  276.       <handler event="click" button="0"><![CDATA[
  277.         event.stopPropagation();
  278.       ]]></handler>
  279.     </handlers>
  280.   </binding>
  281.  
  282.   <!--
  283.      - A column for displaying event boxes in.  One column per
  284.      - day; it manages the layout of the events given via add/deleteEvent.
  285.     -->
  286.   <binding id="calendar-event-column">
  287.     <content>
  288.       <xul:stack anonid="boxstack" flex="1" style="min-width: 1px; min-height: 1px">
  289.         <xul:box anonid="bgbox" flex="1" style="min-width: 1px; min-height: 1px"/>
  290.         <xul:box xbl:inherits="context" anonid="topbox" flex="1" equalsize="always" style="min-width: 1px; min-height: 1px"/>
  291.         <xul:box anonid="fgbox" flex="1" class="fgdragcontainer" style="min-width: 1px; min-height: 1px; overflow:hidden;">
  292.           <xul:box anonid="fgdragspacer" style="display: inherit; overflow: hidden;">
  293.             <xul:spacer flex="1"/>
  294.             <xul:label anonid="fgdragbox-startlabel" class="fgdragbox-label"/>
  295.           </xul:box>
  296.           <xul:box anonid="fgdragbox" class="fgdragbox" />
  297.           <xul:label anonid="fgdragbox-endlabel" class="fgdragbox-label"/>
  298.         </xul:box>
  299.       </xul:stack>
  300.     </content>
  301.  
  302.     <implementation>
  303.       <constructor><![CDATA[
  304.         this.mEvents = Array();
  305.         this.mTimezone = UTC();
  306.       ]]></constructor>
  307.  
  308.       <!-- fields -->
  309.       <field name="mPixPerMin">0.6</field>
  310.       <field name="mStartMin">0*60</field>
  311.       <field name="mEndMin">24*60</field>
  312.       <field name="mDayStartMin">8*60</field>
  313.       <field name="mDayEndMin">17*60</field>
  314.       <field name="mEvents">new Array()</field>
  315.       <field name="mEventMap">null</field>
  316.       <field name="mCalendarView">null</field>
  317.       <field name="mDate">null</field>
  318.       <field name="mTimezone">null</field>
  319.       <field name="mDragState">null</field>
  320.       <field name="mLayoutBatchCount">0</field>
  321.       <!-- Since we'll often be getting many events in rapid succession, this
  322.            timer helps ensure that we don't re-compute the event map too many
  323.            times in a short interval, and therefore improves performance.-->
  324.       <field name="mEventMapTimeout">null</field>
  325.       <!-- Sometimes we need to add resize handlers for columns with special
  326.            widths.  When we relayout, we need to cancel those handlers -->
  327.       <field name="mHandlersToRemove">new Array()</field>
  328.  
  329.       <!-- Set this true so that we know in our onAddItem listener to start
  330.          - modifying an event when it comes back to us as created
  331.         -->
  332.       <field name="mCreatedNewEvent">false</field>
  333.       <field name="mEventToEdit">null</field>
  334.  
  335.       <!-- properties -->
  336.       <property name="pixelsPerMinute">
  337.         <getter><![CDATA[
  338.           return this.mPixPerMin;
  339.         ]]></getter>
  340.         <setter><![CDATA[
  341.           if (val <= 0.0)
  342.             val = 0.01;
  343.           if (val != this.mPixPerMin) {
  344.             this.mPixPerMin = val;
  345.             this.relayout();
  346.           }
  347.           return val;
  348.         ]]></setter>
  349.       </property>
  350.  
  351.       <field name="mSelected">false</field>
  352.       <property name="selected">
  353.         <getter><![CDATA[
  354.           return this.mSelected;
  355.         ]]></getter>
  356.         <setter><![CDATA[
  357.           this.mSelected = val;
  358.           if (this.bgbox && this.bgbox.hasChildNodes()) {
  359.             var child = this.bgbox.firstChild;
  360.             while (child) {
  361.               if (val) {
  362.                 child.setAttribute("selected", "true");
  363.               } else {
  364.                 child.removeAttribute("selected");
  365.               }
  366.               child = child.nextSibling;
  367.             }
  368.           }
  369.           return val;
  370.         ]]></setter>
  371.       </property>
  372.  
  373.       <property name="date">
  374.         <getter><![CDATA[
  375.           return this.mDate;
  376.         ]]></getter>
  377.         <setter><![CDATA[
  378.           this.mDate = val;
  379.  
  380.           if (!compareObjects(val.timezone, this.mTimezone)) {
  381.               //dump ("++ column tz: " + val.timezone + "\n");
  382.               this.mTimezone = val.timezone;
  383.               if (!this.mLayoutBatchCount) {
  384.                   this.recalculateStartEndMinutes();
  385.               }
  386.           }
  387.  
  388.           return val;
  389.         ]]></setter>
  390.       </property>
  391.  
  392.       <property name="calendarView"
  393.         onget="return this.mCalendarView;"
  394.         onset="return (this.mCalendarView = val);" />
  395.  
  396.       <property
  397.         name="topbox"
  398.         readonly="true">
  399.         <getter><![CDATA[
  400.           return document.getAnonymousElementByAttribute(this, "anonid", "topbox");
  401.         ]]></getter>
  402.       </property>
  403.  
  404.       <property
  405.         name="bgbox"
  406.         readonly="true">
  407.         <getter><![CDATA[
  408.           return document.getAnonymousElementByAttribute(this, "anonid", "bgbox");
  409.         ]]></getter>
  410.       </property>
  411.  
  412.       <field name="mFgboxes">null</field>
  413.       <property
  414.         name="fgboxes"
  415.         readonly="true">
  416.         <getter><![CDATA[
  417.           if (this.mFgboxes == null) {
  418.               this.mFgboxes = {
  419.                   box: document.getAnonymousElementByAttribute(this, "anonid", "fgbox"),
  420.                   dragbox: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox"),
  421.                   dragspacer: document.getAnonymousElementByAttribute(this, "anonid", "fgdragspacer"),
  422.                   startlabel: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox-startlabel"),
  423.                   endlabel: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox-endlabel")
  424.               };
  425.           }
  426.           return this.mFgboxes;
  427.         ]]></getter>
  428.       </property>
  429.  
  430.       <property
  431.         name="events"
  432.         readonly="true"
  433.         onget="return this.methods"/>
  434.  
  435.       <field name="mDayOff">false</field>
  436.       <property name="dayOff">
  437.         <getter><![CDATA[
  438.           return this.mDayOff;
  439.         ]]></getter>
  440.         <setter><![CDATA[
  441.           this.mDayOff = val;
  442.           return val;
  443.         ]]></setter>
  444.       </property>
  445.  
  446.       <!-- mEvents -->
  447.       <field name="mSelectedChunks">[]</field>
  448.  
  449.       <method name="selectOccurrence">
  450.         <parameter name="aOccurrence"/>
  451.         <body><![CDATA[
  452.           if (aOccurrence) {
  453.               var chunk = this.findChunkForOccurrence(aOccurrence);
  454.               if (!chunk) {
  455.                   dump("++ Couldn't find chunk to select!!!\n");
  456.                   return;
  457.               }
  458.               chunk.selected = true;
  459.               this.mSelectedChunks.push(chunk);
  460.           }
  461.         ]]></body>
  462.       </method>
  463.  
  464.       <method name="unselectOccurrence">
  465.         <parameter name="aOccurrence"/>
  466.         <body><![CDATA[
  467.           if (aOccurrence) {
  468.               var chunk = this.findChunkForOccurrence(aOccurrence);
  469.               if (!chunk) {
  470.                   dump ("++ Couldn't find chunk to unselect!!!\n");
  471.                   return;
  472.               }
  473.               chunk.selected = false;
  474.               var index = this.mSelectedChunks.indexOf(chunk);
  475.               this.mSelectedChunks.splice(index, 1);
  476.           }
  477.         ]]></body>
  478.       </method>
  479.  
  480.       <method name="findChunkForOccurrence">
  481.         <parameter name="aOccurrence"/>
  482.         <body><![CDATA[
  483.           for each (var chunk in this.mEventBoxes) {
  484.               if (chunk.occurrence.hashId == aOccurrence.hashId) {
  485.                   return chunk;
  486.               }
  487.           }
  488.  
  489.           return null;
  490.         ]]></body>
  491.       </method>
  492.  
  493.       <method name="startLayoutBatchChange">
  494.         <body><![CDATA[
  495.             this.mLayoutBatchCount++;
  496.         ]]></body>
  497.       </method>
  498.       <method name="endLayoutBatchChange">
  499.         <body><![CDATA[
  500.             this.mLayoutBatchCount--;
  501.             if (this.mLayoutBatchCount == 0)
  502.               this.relayout();
  503.         ]]></body>
  504.       </method>
  505.  
  506.       <method name="setAttribute">
  507.         <parameter name="aAttr"/>
  508.         <parameter name="aVal"/>
  509.         <body><![CDATA[
  510.           // this should be done using lookupMethod(), see bug 286629
  511.           var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
  512.  
  513.           if (aAttr == "orient" && this.getAttribute("orient") != aVal) {
  514.               this.relayout();
  515.           }
  516.  
  517.           return ret;
  518.         ]]></body>
  519.       </method>
  520.  
  521.       <method name="internalDeleteEvent">
  522.         <parameter name="aOccurrence"/>
  523.         <body><![CDATA[
  524.            var itemIndex = -1;
  525.            var i;
  526.            for (var i in this.mEvents) {
  527.                occ = this.mEvents[i].event;
  528.                if (occ.hashId == aOccurrence.hashId)
  529.                {
  530.                    itemIndex = i;
  531.                    break;
  532.                }
  533.            }
  534.  
  535.            if (itemIndex != -1) {
  536.                function isNotItem(a) {
  537.                   return a.occurrence.hashId != aOccurrence.hashId;
  538.                }
  539.                this.mSelectedChunks = this.mSelectedChunks.filter(isNotItem);
  540.  
  541.                this.mEvents.splice(itemIndex, 1);
  542.                return true;
  543.            } else {
  544.                return false;
  545.            }
  546.         ]]></body>
  547.       </method>
  548.  
  549.       <method name="recalculateStartEndMinutes">
  550.         <body><![CDATA[
  551.           for each (var chunk in this.mEvents) {
  552.               var mins = this.getStartEndMinutesForOccurrence(chunk.event);
  553.               chunk.startMinute = mins.start;
  554.               chunk.endMinute = mins.end;
  555.           }
  556.  
  557.           this.relayout();
  558.         ]]></body>
  559.       </method>
  560.  
  561.       <!-- NOTE: This function may not return the true start and end time
  562.                  of an occurrence if that occurrence starts or ends on a day
  563.                  different than the day of this column -->
  564.       <method name="getStartEndMinutesForOccurrence">
  565.         <parameter name="aOccurrence"/>
  566.         <body><![CDATA[
  567.            var stdate = aOccurrence.startDate || aOccurrence.entryDate;
  568.            var enddate = aOccurrence.endDate || aOccurrence.dueDate;
  569.  
  570.            if (!compareObjects(stdate.timezone, this.mTimezone)) {
  571.                stdate = stdate.getInTimezone (this.mTimezone);
  572.            }
  573.  
  574.            if (!compareObjects(enddate.timezone, this.mTimezone)) {
  575.                enddate = enddate.getInTimezone (this.mTimezone);
  576.            }
  577.  
  578.            var startHour   = stdate.hour;
  579.            var startMinute = stdate.minute;
  580.            var endHour   = enddate.hour;
  581.            var endMinute = enddate.minute;
  582.  
  583.            // Handle cases where an event begins or ends on a day other than this
  584.            if (stdate.compare(this.mDate) == -1) {
  585.                startHour = 0;
  586.                startMinute = 0;
  587.            }
  588.            if (enddate.compare(this.mDate) == 1) {
  589.                endHour = 24;
  590.                endMinute = 0;
  591.            }
  592.  
  593.            return { start: startHour * 60 + startMinute,
  594.                     end:   endHour   * 60 + endMinute };
  595.         ]]></body>
  596.       </method>
  597.  
  598.       <method name="createChunk">
  599.         <parameter name="aOccurrence"/>
  600.         <body><![CDATA[
  601.            var mins = this.getStartEndMinutesForOccurrence(aOccurrence);
  602.  
  603.            var chunk = {
  604.                startMinute: mins.start,
  605.                endMinute: mins.end,
  606.                event: aOccurrence
  607.            };
  608.            return chunk;
  609.         ]]></body>
  610.       </method>
  611.  
  612.       <method name="addEvent">
  613.         <parameter name="aOccurrence"/>
  614.         <body><![CDATA[
  615.            this.internalDeleteEvent(aOccurrence);
  616.  
  617.            var chunk = this.createChunk(aOccurrence);
  618.            this.mEvents.push(chunk);
  619.            if (this.mEventMapTimeout) {
  620.                clearTimeout(this.mEventMapTimeout);
  621.            }
  622.            var column = this;
  623.  
  624.            if (this.mCreatedNewEvent) {
  625.                this.mEventToEdit = aOccurrence;
  626.            }
  627.            // Fun with scoping...
  628.            this.mEventMapTimeout = setTimeout(function() { column.relayout.call(column) }, 5);
  629.         ]]></body>
  630.       </method>
  631.  
  632.       <method name="deleteEvent">
  633.         <parameter name="aOccurrence"/>
  634.         <body><![CDATA[
  635.            if (this.internalDeleteEvent(aOccurrence))
  636.              this.relayout();
  637.         ]]></body>
  638.       </method>
  639.  
  640.       <method name="clear">
  641.         <body><![CDATA[
  642.           while (this.bgbox && this.bgbox.hasChildNodes())
  643.               this.bgbox.removeChild(this.bgbox.lastChild);
  644.           while (this.topbox && this.topbox.hasChildNodes())
  645.               this.topbox.removeChild(this.topbox.lastChild);
  646.           for each (handler in this.mHandlersToRemove)
  647.               window.removeEventListener("resize", handler, true);
  648.           this.mHandlersToRemove = [];
  649.         ]]></body>
  650.       </method>
  651.  
  652.       <method name="relayout">
  653.         <body><![CDATA[
  654.  
  655.           if (this.mLayoutBatchCount > 0)
  656.               return;
  657.  
  658.           function createXULElement(el) {
  659.               return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
  660.           }
  661.  
  662.           this.clear();
  663.  
  664.           var orient = this.getAttribute("orient");
  665.           var otherorient = "vertical";
  666.           if (!orient) orient = "horizontal";
  667.           if (orient == "vertical") otherorient = "horizontal";
  668.  
  669.           // bgbox is used mainly for drawing the grid.  at some point it may
  670.           // also be used for all-day events.
  671.           this.bgbox.setAttribute("orient", orient);
  672.  
  673.           var theMin = this.mStartMin;
  674.           while (theMin < this.mEndMin) {
  675.               var dur = theMin % 60;
  676.               theMin += dur;
  677.               if (dur == 0) dur = 60;
  678.  
  679.               var box = createXULElement("spacer");
  680.               // we key off this in a CSS selector
  681.               box.setAttribute("orient", orient);
  682.               box.setAttribute("class", "calendar-event-column-linebox");
  683.  
  684.               if (this.mSelected) {
  685.                   box.setAttribute("selected", "true");
  686.               }
  687.               if (this.mDayOff) {
  688.                   box.setAttribute("weekend", "true");
  689.               }
  690.               if (theMin < this.mDayStartMin || theMin >= this.mDayEndMin) {
  691.                   box.setAttribute("off-time", "true");
  692.               }
  693.  
  694.               // Carry forth the day relation
  695.               box.setAttribute("relation", this.getAttribute("relation"));
  696.  
  697.               // calculate duration pixel as the difference between
  698.               // start pixel and end pixel to avoid rounding errors.
  699.               var startPix = Math.round(theMin * this.mPixPerMin);
  700.               var endPix   = Math.round((theMin + dur) * this.mPixPerMin);
  701.               var durPix   = endPix - startPix;
  702.               if (orient == "vertical")
  703.                   box.setAttribute("height", durPix);
  704.               else
  705.                   box.setAttribute("width", durPix);
  706.  
  707.               box.setAttribute("style", "min-width: 1px; min-height: 1px;");
  708.  
  709.               this.bgbox.appendChild(box);
  710.               theMin += 60;
  711.           }
  712.  
  713.           // fgbox is used for dragging events
  714.           this.fgboxes.box.setAttribute("orient", orient);
  715.           document.getAnonymousElementByAttribute(this, "anonid", "fgdragspacer").setAttribute("orient", orient);
  716.  
  717.           // this one is set to otherorient, since it will contain
  718.           // child boxes set to "orient" (one for each set of
  719.           // overlapping event areas)
  720.           this.topbox.setAttribute("orient", otherorient);
  721.  
  722.           this.mEventMap = this.computeEventMap();
  723.           this.mEventBoxes = new Array();
  724.  
  725.           if (!this.mEventMap.length) {
  726.               return;
  727.           }
  728.  
  729.           // First of all we create a xul:stack which
  730.           // will hold all events for this event column.
  731.           // The stack will be grouped below .../calendar-event-column/stack/topbox.
  732.           var stack = createXULElement("stack");
  733.           stack.setAttribute("flex", "1");
  734.           this.topbox.appendChild(stack);
  735.  
  736.           var boxToEdit;
  737.  
  738.           for each (var layer in this.mEventMap) {
  739.  
  740.               // The event-map (this.mEventMap) contains an array of layers.
  741.               // For each layer we create a box below the stack just created above.
  742.               // So each different layer lives in a box that's contained in the stack.
  743.               var xulColumn = createXULElement("box");
  744.               xulColumn.setAttribute("orient", otherorient);
  745.               xulColumn.setAttribute("flex", "1");
  746.               xulColumn.setAttribute("style", "min-width: 1px; min-height: 1px;");
  747.               stack.appendChild(xulColumn);
  748.  
  749.               var numBlocksInserted = 0;
  750.  
  751.               // Each layer contains a list of the columns that
  752.               // need to be created for a span.
  753.               for each (var column in layer) {
  754.  
  755.                   var innerColumn = createXULElement("box");
  756.                   innerColumn.setAttribute("orient", orient);
  757.                   innerColumn.setAttribute("flex", 1);
  758.                   var style = "min-width: 1px; min-height: 1px;";
  759.  
  760.                   if (column.specialSpan) {
  761.                       // Special case when we can't simply rely on flex.  Only
  762.                       // happens when we have columns in the layer that need to
  763.                       // be different sizes.  That is, when we have a colSpan
  764.                       // of 2, a total of 5 columns, and a startCol of 1.
  765.                       // Then, our columns need to be laid out as 1/5, 2/5, 2/5.
  766.                       if (orient == "vertical") {
  767.                           style += "max-width: " +
  768.                                    column.specialSpan * this.topbox.boxObject.width +
  769.                                    "px;";
  770.                       } else {
  771.                           style += "max-height: " +
  772.                                    column.specialSpan * this.topbox.boxObject.height +
  773.                                    "px;";
  774.                       }
  775.  
  776.                       // Now we need to set up a resize listener, since without
  777.                       // it our box will look funny if the window resizes.  This
  778.                       // requires us to be *very* careful about closures, because
  779.                       // we don't want things like column.specialSpan to change
  780.                       function colResizeHandler(aInnerCol, aCalCol, aSpan) {
  781.                           this.handleEvent = function(aEvent) {
  782.                               var resizeStyle = "min-width: 1px; min-height: 1px;";
  783.                               if (orient == "vertical") {
  784.                                   resizeStyle += "max-width: " +
  785.                                                  aSpan * aCalCol.topbox.boxObject.width +
  786.                                                  "px;";
  787.                               } else {
  788.                                   resizeStyle += "max-height: " +
  789.                                                  aSpan * aCalCol.topbox.boxObject.height +
  790.                                                  "px;";
  791.                               }
  792.                               aInnerCol.setAttribute("style", resizeStyle);
  793.                           };
  794.                       }
  795.                       var myResizeHandler = new colResizeHandler(innerColumn, this, column.specialSpan);
  796.                       this.mHandlersToRemove.push(myResizeHandler);
  797.                       window.addEventListener("resize", myResizeHandler, true);
  798.                   }
  799.                   innerColumn.setAttribute("style", style);
  800.  
  801.                   xulColumn.appendChild(innerColumn);
  802.  
  803.                   var curTime = 0;
  804.                   for each (var chunk in column) {
  805.                       var duration = chunk.duration;
  806.                       if (!duration) {
  807.                           continue;
  808.                       }
  809.  
  810.                       if (chunk.event) {
  811.                           var chunkBox = createXULElement("calendar-event-box");
  812.                           chunkBox.setAttribute("context", this.getAttribute("item-context") || this.getAttribute("context"));
  813.                           chunkBox.setAttribute("style", "min-width: 1px; min-height: 1px;");
  814.                           chunkBox.setAttribute("orient", orient);
  815.                           var durMinutes = duration.inSeconds / 60;
  816.                           if (orient == "vertical") {
  817.                               chunkBox.setAttribute("height", durMinutes * this.mPixPerMin);
  818.                           } else {
  819.                               chunkBox.setAttribute("width", durMinutes * this.mPixPerMin);
  820.                           }
  821.  
  822.                           if (!isCalendarWritable(chunk.event.calendar)) {
  823.                               chunkBox.setAttribute("readOnly", "true");
  824.                           }
  825.  
  826.                           if (chunk.event.hashId in this.calendarView.mFlashingEvents) {
  827.                               chunkBox.setAttribute("flashing", "true");
  828.                           }
  829.  
  830.                           innerColumn.appendChild(chunkBox);
  831.  
  832.                           chunkBox.calendarView = this.calendarView;
  833.                           chunkBox.occurrence = chunk.event;
  834.                           chunkBox.parentColumn = this;
  835.  
  836.                           this.mEventBoxes.push(chunkBox);
  837.  
  838.                           if (this.mEventToEdit &&
  839.                               chunkBox.occurrence.hashId == this.mEventToEdit.hashId) {
  840.                               boxToEdit = chunkBox;
  841.                           }
  842.                       } else {
  843.                           var chunkBox = createXULElement("spacer");
  844.                           chunkBox.setAttribute("context", this.getAttribute("context"));
  845.                           chunkBox.setAttribute("style", "min-width: 1px; min-height: 1px;");
  846.                           chunkBox.setAttribute("orient", orient);
  847.                           chunkBox.setAttribute("class", "calendar-empty-space-box");
  848.                           innerColumn.appendChild(chunkBox);
  849.  
  850.                           var durMinutes = duration.inSeconds / 60;
  851.                           if (orient == "vertical") {
  852.                               chunkBox.setAttribute("height", durMinutes * this.mPixPerMin);
  853.                           } else {
  854.                               chunkBox.setAttribute("width", durMinutes * this.mPixPerMin);
  855.                           }
  856.                       }
  857.                   }
  858.  
  859.                   numBlocksInserted++;
  860.                   curTime += duration;
  861.               }
  862.  
  863.               if (boxToEdit) {
  864.                   this.mCreatedNewEvent = false;
  865.                   this.mEventToEdit = null;
  866.                   boxToEdit.startEditing();
  867.               }
  868.  
  869.               if (numBlocksInserted == 0) {
  870.                   // if we didn't insert any blocks, then
  871.                   // forget about this column
  872.                   stack.removeChild(xulColumn);
  873.               }
  874.           }
  875.         ]]></body>
  876.       </method>
  877.  
  878.       <method name="computeEventMap">
  879.         <body><![CDATA[
  880.           /* We're going to create a series of 'blobs'.  A blob is a series of
  881.            * events that create a continuous block of busy time.  In other
  882.            * words, a blob ends when there is some time such that no events
  883.            * occupy that time.
  884.            *
  885.            * Each blob will be an array of objects with the following properties:
  886.            *    item:     the event/task
  887.            *    startCol: the starting column to display the event in (0-indexed)
  888.            *    colSpan:  the number of columns the item spans
  889.            *
  890.            * An item with no conflicts will have startCol: 0 and colSpan: 1.
  891.            */
  892.           var blobs = new Array();
  893.           var currentBlob = new Array();
  894.  
  895.           function sortByStart(a, b) {
  896.               // If you pass in tasks without both entry and due dates, I will
  897.               // kill you
  898.               var aStart = a.event.startDate || a.event.entryDate;
  899.               var bStart = b.event.startDate || b.event.entryDate;
  900.               var startComparison = aStart.compare(bStart);
  901.               if (startComparison != 0) {
  902.                   return startComparison;
  903.               } else {
  904.                   var aEnd = a.event.endDate || a.event.dueDate;
  905.                   var bEnd = b.event.endDate || b.event.dueDate;
  906.                   // If the items start at the same time, return the longer one
  907.                   // first
  908.                   return bEnd.compare(aEnd);
  909.               }
  910.           }
  911.  
  912.           this.mEvents.sort(sortByStart);
  913.  
  914.           // The end time of the last ending event in the entire blob
  915.           var latestItemEnd;
  916.  
  917.           // This array keeps track of the last (latest ending) item in each of
  918.           // the columns of the current blob. We could reconstruct this data at
  919.           // any time by looking at the items in the blob, but that would hurt
  920.           // perf.
  921.           var colEndArray = new Array();
  922.  
  923.           /* Go through a 3 step process to try and place each item.
  924.            * Step 1: Look for an existing column with room for the item.
  925.            * Step 2: Look for a previously placed item that can be shrunk in
  926.            *         width to make room for the item.
  927.            * Step 3: Give up and create a new column for the item.
  928.            *
  929.            * (The steps are explained in more detail as we come to them)
  930.            */
  931.           for (var i in this.mEvents) {
  932.               var item = this.mEvents[i].event;
  933.               var itemStart = item.startDate || item.entryDate;
  934.               var itemEnd = item.endDate || item.dueDate;
  935.               if (!latestItemEnd) {
  936.                   latestItemEnd = itemEnd;
  937.               }
  938.               if (currentBlob.length && latestItemEnd &&
  939.                   itemStart.compare(latestItemEnd) != -1) {
  940.                   // We're done with this current blob because item starts
  941.                   // after the last event in the current blob ended.
  942.                   blobs.push({blob: currentBlob, totalCols: colEndArray.length});
  943.  
  944.                   // Reset our variables
  945.                   currentBlob = new Array();
  946.                   colEndArray = new Array();
  947.               }
  948.  
  949.               // Place the item in its correct place in the blob
  950.               var placedItem = false;
  951.  
  952.               // Step 1
  953.               // Look for a possible column in the blob that has been left open. This
  954.               // would happen if we already have multiple columns but some of
  955.               // the cols have events before latestItemEnd.  For instance
  956.               //       |      |      |
  957.               //       |______|      |
  958.               //       |ev1   |______|
  959.               //       |      |ev2   |
  960.               //       |______|      |
  961.               //       |      |      |
  962.               //       |OPEN! |      |<--Our item's start time might be here
  963.               //       |      |______|
  964.               //       |      |      |
  965.               //
  966.               // Remember that any time we're starting a new blob, colEndArray
  967.               // will be empty, but that's ok.
  968.               for (var ii=0; ii<colEndArray.length; ++ii) {
  969.                   var colEnd = colEndArray[ii].endDate || colEndArray[ii].dueDate;
  970.                   if (colEnd.compare(itemStart) != 1) {
  971.                       // Yay, we can jump into this column
  972.                       colEndArray[ii] = item;
  973.  
  974.                       // Check and see if there are any adjacent columns we can
  975.                       // jump into as well.
  976.                       var lastCol = Number(ii) + 1;
  977.                       while (lastCol < colEndArray.length) {
  978.                           var nextColEnd = colEndArray[lastCol].endDate ||
  979.                                            colEndArray[lastCol].dueDate;
  980.                           // If the next column's item ends after we start, we
  981.                           // can't expand any further
  982.                           if (nextColEnd.compare(itemStart) == 1) {
  983.                               break;
  984.                           }
  985.                           colEndArray[lastCol] = item;
  986.                           lastCol++;
  987.                       }
  988.                       // Now construct the info we need to push into the blob
  989.                       currentBlob.push({item: item,
  990.                                         startCol: ii,
  991.                                         colSpan: lastCol - ii});
  992.  
  993.                       // Update latestItemEnd
  994.                       if (latestItemEnd &&
  995.                           itemEnd.compare(latestItemEnd) == 1) {
  996.                           latestItemEnd = itemEnd;
  997.                       }
  998.                       placedItem = true;
  999.                       break; // Stop iterating through colEndArray
  1000.                   }
  1001.               }
  1002.  
  1003.               if (placedItem) {
  1004.                   // Go get the next item
  1005.                   continue;
  1006.               }
  1007.  
  1008.               // Step 2
  1009.               // OK, all columns (if there are any) overlap us.  Look if the
  1010.               // last item in any of the last items in those columns is taking
  1011.               // up 2 or more cols. If so, shrink it and stick the item in the
  1012.               // created space. For instance
  1013.               //       |______|______|______|
  1014.               //       |ev1   |ev3   |ev4   |
  1015.               //       |      |      |      |
  1016.               //       |      |______|      |
  1017.               //       |      |      |______|
  1018.               //       |      |_____________|
  1019.               //       |      |ev2          |
  1020.               //       |______|             |<--If our item's start time is
  1021.               //       |      |_____________|   here, we can shrink ev2 and jump
  1022.               //       |      |      |      |   in column #3
  1023.               //
  1024.               for (var jj=1; jj<colEndArray.length; ++jj) {
  1025.                   if (colEndArray[jj].hashId == colEndArray[jj-1].hashId) {
  1026.                       // Good we found a item that spanned multiple columns.
  1027.                       // Find it in the blob so we can modify its properties
  1028.                       for (var kk in currentBlob) {
  1029.                           if (currentBlob[kk].item.hashId == colEndArray[jj].hashId) {
  1030.                               // Take all but the first spot that the item spanned
  1031.                               var spanOfShrunkItem = currentBlob[kk].colSpan;
  1032.                               currentBlob.push({item: item,
  1033.                                                 startCol: Number(currentBlob[kk].startCol) + 1,
  1034.                                                 colSpan: spanOfShrunkItem - 1});
  1035.  
  1036.                               // Update colEndArray
  1037.                               for (var ll = jj; ll < jj + spanOfShrunkItem - 1; ll++) {
  1038.                                   colEndArray[ll] = item;
  1039.                               }
  1040.  
  1041.                               // Modify the data on the old item
  1042.                               currentBlob[kk] = {item: currentBlob[kk].item,
  1043.                                                  startCol: currentBlob[kk].startCol,
  1044.                                                  colSpan: 1};
  1045.                               // Update latestItemEnd
  1046.                               if (latestItemEnd &&
  1047.                                   itemEnd.compare(latestItemEnd) == 1) {
  1048.                                   latestItemEnd = itemEnd;
  1049.                               }
  1050.                               break; // Stop iterating through currentBlob
  1051.                           }
  1052.                       }
  1053.                       placedItem = true;
  1054.                       break; // Stop iterating through colEndArray
  1055.                   }
  1056.               }
  1057.  
  1058.               if (placedItem) {
  1059.                   // Go get the next item
  1060.                   continue;
  1061.               }
  1062.  
  1063.               // Step 3
  1064.               // Guess what? We still haven't placed the item.  We need to
  1065.               // create a new column for it.
  1066.  
  1067.               // All the items in the last column, except for the one* that
  1068.               // conflicts with the item we're trying to place, need to have
  1069.               // their span extended by 1, since we're adding the new column
  1070.               //
  1071.               // * Note that there can only be one, because we sorted our
  1072.               //   events by start time, so this event must start later than
  1073.               //   the start of any possible conflicts.
  1074.               var lastColNum = colEndArray.length;
  1075.               for (var mm in currentBlob) {
  1076.                   var mmEnd = currentBlob[mm].item.endDate || currentBlob[mm].item.dueDate
  1077.                   if (currentBlob[mm].startCol + currentBlob[mm].colSpan == lastColNum &&
  1078.                       mmEnd.compare(itemStart) != 1) {
  1079.                       currentBlob[mm] = {item: currentBlob[mm].item,
  1080.                                          startCol: currentBlob[mm].startCol,
  1081.                                          colSpan: currentBlob[mm].colSpan + 1};
  1082.                   }
  1083.               }
  1084.               currentBlob.push({item: item,
  1085.                                 startCol: colEndArray.length,
  1086.                                 colSpan: 1});
  1087.               colEndArray.push(item);
  1088.  
  1089.               // Update latestItemEnd
  1090.               if (latestItemEnd && itemEnd.compare(latestItemEnd) == 1) {
  1091.                   latestItemEnd = itemEnd;
  1092.               }
  1093.               // Go get the next item
  1094.           }
  1095.           // Add the last blob
  1096.           blobs.push({blob: currentBlob,
  1097.                       totalCols: colEndArray.length});
  1098.  
  1099.           return this.setupBoxStructure(blobs);
  1100.         ]]></body>
  1101.       </method>
  1102.  
  1103.       <method name="setupBoxStructure">
  1104.         <parameter name="aBlobs"/>
  1105.         <body><![CDATA[
  1106.           // This is actually going to end up being a 3-d array
  1107.           // 1st dimension: "layers", sets of columns of events that all
  1108.           //                should have equal width*
  1109.           // 2nd dimension: "columns", individual columns of non-conflicting
  1110.           //                items
  1111.           // 3rd dimension: "chunks", individual items or placeholders for
  1112.           //                the blank time in between them
  1113.           //
  1114.           // * Note that 'equal width' isn't strictly correct.  If we're
  1115.           //   oriented differently, it will be height (and we'll have rows
  1116.           //   not columns).  What's more, in the 'specialSpan' case, the
  1117.           //   columns won't actually have the same size, but will only all
  1118.           //   be multiples of a common size.  See the note in the relayout
  1119.           //   function for more info on this (fairly rare) case.
  1120.           var layers = [];
  1121.  
  1122.           // When we start a new blob, move to a new set of layers
  1123.           var layerOffset = 0;
  1124.           for each (var glob in aBlobs) {
  1125.  
  1126.               var layerArray = [];
  1127.               var layerCounter = 1;
  1128.  
  1129.               for each (var data in glob.blob) {
  1130.  
  1131.                   // from the item at hand we need to figure out on which
  1132.                   // layer and on which column it should go.
  1133.                   var layerIndex;
  1134.                   var specialSpan = null;
  1135.  
  1136.                   // each blob receives its own layer, that's the first part of the story. within
  1137.                   // a given blob we need to distribute the items on different layers depending on
  1138.                   // the number of columns each item spans. if each item just spans a single column
  1139.                   // the blob will cover *one* layer. if the blob contains items that span more than
  1140.                   // a single column, this blob will cover more than one layer. the algorithm places
  1141.                   // the items on the first layer in the case an item covers a single column. new layers
  1142.                   // are introduced based on the start column and number of spanning columns of an item.
  1143.                   if (data.colSpan != 1) {
  1144.                       var index = glob.totalCols * data.colSpan + data.startCol;
  1145.                       layerIndex = layerArray[index];
  1146.                       if (!layerIndex) {
  1147.                           layerIndex = layerCounter++;
  1148.                           layerArray[index] = layerIndex;
  1149.                       }
  1150.                       var offset = ((glob.totalCols - data.colSpan) % glob.totalCols)
  1151.                       if (offset != 0) {
  1152.                           specialSpan = data.colSpan / glob.totalCols;
  1153.                       }
  1154.                   } else {
  1155.                       layerIndex = 0;
  1156.                   }
  1157.                   layerIndex += layerOffset;
  1158.  
  1159.                   // Make sure there's room to insert stuff
  1160.                   while (layerIndex >= layers.length) {
  1161.                       layers.push([]);
  1162.                   }
  1163.  
  1164.                   while (data.startCol >= layers[layerIndex].length) {
  1165.                       layers[layerIndex].push([]);
  1166.                       if (specialSpan) {
  1167.                           layers[layerIndex][layers[layerIndex].length - 1].specialSpan = 1 / glob.totalCols;
  1168.                       }
  1169.                   }
  1170.  
  1171.                   // we now retrieve the column from 'layerIndex' and 'startCol'.
  1172.                   var col = layers[layerIndex][data.startCol];
  1173.                   if (specialSpan) {
  1174.                       col.specialSpan = specialSpan;
  1175.                   }
  1176.  
  1177.                   // take into account that items can span several days.
  1178.                   // that's why i'm clipping the start- and end-time to the
  1179.                   // timespan of this column.
  1180.                   var start = data.item.startDate || data.item.entryDate;
  1181.                   if (!compareObjects(start.timezone, this.mTimezone)) {
  1182.                       start = start.getInTimezone(this.mTimezone);
  1183.                   }
  1184.                   if (start.year  != this.date.year ||
  1185.                       start.month != this.date.month ||
  1186.                       start.day   != this.date.day) {
  1187.                       start = start.clone();
  1188.                       start.resetTo(this.date.year,
  1189.                                     this.date.month,
  1190.                                     this.date.day,
  1191.                                     0,this.mStartMin,0,
  1192.                                     start.timezone);
  1193.                   }
  1194.                   var end = data.item.endDate || data.item.dueDate;
  1195.                   if (!compareObjects(end.timezone, this.mTimezone)) {
  1196.                       end = end.getInTimezone(this.mTimezone);
  1197.                   }
  1198.                   if (end.year  != this.date.year ||
  1199.                       end.month != this.date.month ||
  1200.                       end.day   != this.date.day) {
  1201.                       end = end.clone();
  1202.                       end.resetTo(this.date.year,
  1203.                                   this.date.month,
  1204.                                   this.date.day,
  1205.                                   0,this.mEndMin,0,
  1206.                                   end.timezone);
  1207.                   }
  1208.                   var prevEnd;
  1209.                   if (col.length > 0) {
  1210.                       // Fill in time gaps with a placeholder
  1211.                       prevEnd = col[col.length - 1].endDate.clone();
  1212.                   } else {
  1213.                       // First event in the column, add a placeholder for the
  1214.                       // blank time from this.mStartMin to the event's start
  1215.                       prevEnd = start.clone();
  1216.                       prevEnd.hour = 0;
  1217.                       prevEnd.minute = this.mStartMin;
  1218.                   }
  1219.                   prevEnd.timezone = floating();
  1220.                   // the reason why we need to calculate time durations
  1221.                   // based on floating timezones is that we need avoid
  1222.                   // dst gaps in this case. converting the date/times to
  1223.                   // floating conveys this idea in a natural way. note that
  1224.                   // we explicitly don't use getInTimezone() as it would
  1225.                   // be slightly more expensive in terms of performance.
  1226.                   var floatstart = start.clone();
  1227.                   floatstart.timezone = floating();
  1228.                   var dur = floatstart.subtractDate(prevEnd);
  1229.                   if (dur.inSeconds) {
  1230.                       col.push({duration: dur});
  1231.                   }
  1232.                   var floatend = end.clone();
  1233.                   floatend.timezone = floating();
  1234.                   col.push({event: data.item,
  1235.                             endDate: end,
  1236.                             duration: floatend.subtractDate(floatstart)});
  1237.               }
  1238.               layerOffset = layers.length;
  1239.           }
  1240.           return layers;
  1241.         ]]></body>
  1242.       </method>
  1243.  
  1244.       <!--
  1245.          - Event sweep handlers
  1246.         -->
  1247.       <method name="onEventSweepMouseMove">
  1248.         <parameter name="event"/>
  1249.         <body><![CDATA[
  1250.           var col = document.calendarEventColumnDragging;
  1251.           if (!col) return;
  1252.  
  1253.           if (event.clientX < (event.target.boxObject.x) ||
  1254.               event.clientX > (event.target.boxObject.x + event.target.boxObject.width) ||
  1255.               event.clientY < (event.target.boxObject.y) ||
  1256.               event.clientY > (event.target.boxObject.y + event.target.boxObject.height)) {
  1257.               
  1258.               var dragState = col.mDragState;
  1259.               col.fgboxes.dragbox.removeAttribute("dragging");
  1260.               col.fgboxes.box.removeAttribute("dragging");
  1261.               window.removeEventListener("mousemove", col.onEventSweepMouseMove, false);
  1262.               window.removeEventListener("mouseup", col.onEventSweepMouseUp, false);
  1263.               document.calendarEventColumnDragging = null;
  1264.               col.mDragState = null;
  1265.               
  1266.               var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
  1267.                                 getService(Components.interfaces.nsIDragService);
  1268.               var transfer = Components.classes["@mozilla.org/widget/transferable;1"].
  1269.                              createInstance(Components.interfaces.nsITransferable);
  1270.               transfer.addDataFlavor("text/calendar");
  1271.  
  1272.               var item = dragState.dragOccurrence;
  1273.           
  1274.               // the multiday view currently exhibits a less than optimal strategy
  1275.               // in terms of item selection. items don't get automatically selected
  1276.               // when clicked and dragged, as to differentiate inline editing from
  1277.               // the act of selecting an event. but the application internal drop
  1278.               // targets will ask for selected items in order to pull the data from
  1279.               // the packets. that's why we need to make sure at least the currently
  1280.               // dragged event is contained in the set of selected items.
  1281.               var selectedItems = currentView().getSelectedItems({});
  1282.               if (!selectedItems.some(
  1283.                   function (aItem) {
  1284.                       return (aItem.hashId == item.hashId);
  1285.                   })) {
  1286.                   col.calendarView.setSelectedItems(1,
  1287.                       [event.ctrlKey ? item.parentItem : item]);
  1288.               }
  1289.  
  1290.               var flavourProvider = {
  1291.                   QueryInterface: function(aIID) {
  1292.                       ensureIID(
  1293.                           [ Components.interfaces.nsIFlavorDataProvider,
  1294.                             Components.interfaces.nsISupports], aIID);
  1295.                       return this;
  1296.                   },
  1297.                   item: item,
  1298.  
  1299.                   getFlavorData: function(aInTransferable, aInFlavor, aOutData, aOutDataLen) {
  1300.                       if ((aInFlavor == "application/vnd.x-moz-cal-event") ||
  1301.                           (aInFlavor == "application/vnd.x-moz-cal-task")) {
  1302.                           aOutData.value = this.item;
  1303.                           aOutDataLen.value = 1;
  1304.                       } else {
  1305.                           ASSERT(false, "error:"+aInFlavor);
  1306.                       }
  1307.                   }
  1308.               };
  1309.  
  1310.               if (isEvent(item)) {
  1311.                 transfer.addDataFlavor("application/vnd.x-moz-cal-event");
  1312.                 transfer.setTransferData("application/vnd.x-moz-cal-event", flavourProvider, 0);
  1313.               } else if (isToDo(item)) {
  1314.                 transfer.addDataFlavor("application/vnd.x-moz-cal-task");
  1315.                 transfer.setTransferData("application/vnd.x-moz-cal-task", flavourProvider, 0);
  1316.               }
  1317.  
  1318.               var serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"].
  1319.                                           createInstance(Components.interfaces.calIIcsSerializer);
  1320.               serializer.addItems([item], 1);
  1321.               
  1322.               var supportsString = Components.classes["@mozilla.org/supports-string;1"].
  1323.                                    createInstance(Components.interfaces.nsISupportsString);
  1324.               supportsString.data = serializer.serializeToString();
  1325.               transfer.setTransferData("text/calendar", supportsString, supportsString.data.length*2);
  1326.               transfer.setTransferData("text/unicode", supportsString, supportsString.data.length*2);
  1327.  
  1328.               var action = dragService.DRAGDROP_ACTION_MOVE;
  1329.               var supArray = Components.classes["@mozilla.org/supports-array;1"].
  1330.                              createInstance(Components.interfaces.nsISupportsArray);
  1331.               supArray.AppendElement(transfer);
  1332.  
  1333.               dragService.invokeDragSession(col, supArray, null, action);
  1334.               
  1335.               return;
  1336.           }
  1337.  
  1338.           var dragState = col.mDragState;
  1339.  
  1340.           col.fgboxes.box.setAttribute("dragging", "true");
  1341.           col.fgboxes.dragbox.setAttribute("dragging", "true");
  1342.  
  1343.           // check if we need to jump a column
  1344.           if (dragState.dragType == "move") {
  1345.               newcol = col.calendarView.findColumnForClientPoint(event.screenX, event.screenY);
  1346.               if (newcol && newcol != col) {
  1347.                   // kill our drag state
  1348.                   col.fgboxes.dragbox.removeAttribute("dragging");
  1349.                   col.fgboxes.box.removeAttribute("dragging");
  1350.  
  1351.                   // jump ship
  1352.                   newcol.acceptInProgressSweep(dragState);
  1353.  
  1354.                   // restart event handling
  1355.                   col.onEventSweepMouseMove(event);
  1356.  
  1357.                   return;
  1358.               }
  1359.           }
  1360.  
  1361.           var pos;
  1362.           var sizeattr;
  1363.           if (col.getAttribute("orient") == "vertical") {
  1364.               pos = event.screenY - col.parentNode.boxObject.screenY - dragState.mouseOffset;
  1365.               sizeattr = "height";
  1366.           } else {
  1367.               pos = event.screenX - col.parentNode.boxObject.screenX - dragState.mouseOffset;
  1368.               sizeattr = "width";
  1369.           }
  1370.           // don't let pos go outside the window edges
  1371.           if (pos < 0)
  1372.               pos = 0;
  1373.  
  1374.           // snap to 15 minute intervals
  1375.           var interval = col.mPixPerMin * 15;
  1376.           var curmin = Math.floor(pos/interval) * 15;
  1377.           var deltamin = curmin - dragState.origMin;
  1378.  
  1379.           if (dragState.dragType == "new") {
  1380.               if (deltamin < 0) {
  1381.                   dragState.startMin = dragState.origMin + deltamin;
  1382.                   dragState.endMin = dragState.origMin;
  1383.               } else {
  1384.                   dragState.startMin = dragState.origMin;
  1385.                   dragState.endMin = dragState.origMin + deltamin;
  1386.               }
  1387.           } else if (dragState.dragType == "move") {
  1388.               // if we're moving, we can only move the start, and the end has to be exactly start+duration
  1389.               dragState.startMin = dragState.origMin + deltamin;
  1390.               dragState.endMin = dragState.startMin + dragState.limitDurationMin;
  1391.           } else if (dragState.dragType == "modify-start") {
  1392.               // if we're modifying the start, the end time is fixed.
  1393.               dragState.startMin = dragState.origMin + deltamin;
  1394.               dragState.endMin = dragState.limitEndMin;
  1395.  
  1396.               // but we need to not go past the end; if we hit
  1397.               // the end, then we'll clamp to the previous 15-min interval
  1398.               if (dragState.endMin <= dragState.startMin)
  1399.                   dragState.startMin = Math.floor((dragState.endMin - 15) / 15) * 15;
  1400.           } else if (dragState.dragType == "modify-end") {
  1401.               // if we're modifying the end, the start time is fixed, and we'll always
  1402.               // set the spacer to a constant size.
  1403.               dragState.startMin = dragState.limitStartMin;
  1404.               dragState.endMin = dragState.origMin + deltamin;
  1405.  
  1406.               // but we need to not go past the start; if we hit
  1407.               // the start, then we'll clamp to the next 15-min interval
  1408.               if (dragState.endMin <= dragState.startMin)
  1409.                   dragState.endMin = Math.floor((dragState.startMin + 15) / 15) * 15;
  1410.           }
  1411.  
  1412.           // update the box sizes
  1413.           col.fgboxes.dragspacer.setAttribute(sizeattr, dragState.startMin * col.mPixPerMin);
  1414.           col.fgboxes.dragbox.setAttribute(sizeattr, Math.abs((dragState.endMin - dragState.startMin) * col.mPixPerMin));
  1415.  
  1416.           // update the label
  1417.           col.updateDragLabels();
  1418.         ]]></body>
  1419.       </method>
  1420.  
  1421.       <method name="onEventSweepMouseUp">
  1422.         <parameter name="event"/>
  1423.         <body><![CDATA[
  1424.           var col = document.calendarEventColumnDragging;
  1425.           if (!col) return;
  1426.  
  1427.           var dragState = col.mDragState;
  1428.  
  1429.           col.fgboxes.dragbox.removeAttribute("dragging");
  1430.           col.fgboxes.box.removeAttribute("dragging");
  1431.  
  1432.           window.removeEventListener("mousemove", col.onEventSweepMouseMove, false);
  1433.           window.removeEventListener("mouseup", col.onEventSweepMouseUp, false);
  1434.  
  1435.           document.calendarEventColumnDragging = null;
  1436.  
  1437.           // if the user didn't sweep out at least a few pixels, ignore
  1438.           // unless we're in a different column
  1439.           if (dragState.origColumn == col) {
  1440.               var ignore = false;
  1441.               if (col.getAttribute("orient") == "vertical") {
  1442.                   if (Math.abs(event.screenY - dragState.origLoc) < 3)
  1443.                       ignore = true;
  1444.               } else {
  1445.                   if (Math.abs(event.screenX - dragState.origLoc) < 3)
  1446.                       ignore = true;
  1447.               }
  1448.  
  1449.               if (ignore) {
  1450.                   document.calendarEventColumnDragging = null;
  1451.                   col.mDragState = null;
  1452.                   return;
  1453.               }
  1454.           }
  1455.  
  1456.           var newStart;
  1457.           var newEnd;
  1458.           var startTZ;
  1459.           var endTZ;
  1460.  
  1461.           if (dragState.dragType == "new") {
  1462.               newStart = col.mDate.clone();
  1463.               newStart.isDate = false;
  1464.               newEnd = col.mDate.clone();
  1465.               newEnd.isDate = false;
  1466.           } else {
  1467.               var oldStart = dragState.dragOccurrence.startDate || dragState.dragOccurrence.entryDate;
  1468.               var oldEnd = dragState.dragOccurrence.endDate || dragState.dragOccurrence.dueDate;
  1469.               newStart = oldStart.clone();
  1470.               newEnd = oldEnd.clone();
  1471.  
  1472.               // Our views are pegged to the default timezone.  If the event
  1473.               // isn't also in the timezone, we're going to need to do some
  1474.               // tweaking.  We could just do this for every eventm but
  1475.               // getInTimezone is slow, so it's much better to only do this
  1476.               // when the timezones actually differ from the view's.
  1477.               if (this.mTimezone != newStart.timezone ||
  1478.                   this.mTimezone != newEnd.timezone) {
  1479.                   startTZ = newStart.timezone;
  1480.                   endTZ = newEnd.timezone;
  1481.                   newStart = newStart.getInTimezone(col.calendarView.mTimezone);
  1482.                   newEnd = newEnd.getInTimezone(col.calendarView.mTimezone);
  1483.               }
  1484.           }
  1485.  
  1486.           var dragDay = col.mDate;
  1487.  
  1488.           if (dragState.dragType == "modify-start" ||
  1489.               dragState.dragType == "new") {
  1490.               newStart.resetTo(dragDay.year, dragDay.month, dragDay.day,
  1491.                                0, dragState.startMin + col.mStartMin, 0,
  1492.                                newStart.timezone);
  1493.           }
  1494.  
  1495.           if (dragState.dragType == "modify-end" ||
  1496.               dragState.dragType == "new") {
  1497.               newEnd.resetTo(dragDay.year, dragDay.month, dragDay.day,
  1498.                              0, dragState.endMin + col.mStartMin, 0,
  1499.                              newEnd.timezone);
  1500.           }
  1501.  
  1502.           if (dragState.dragType == "move") {
  1503.               // Figure out how much the event moved.
  1504.               var duration = col.mDate.subtractDate(dragState.origDate);
  1505.               var minutes = dragState.startMin - dragState.origMin;
  1506.  
  1507.               // Since both boxDate and beginMove are dates (note datetimes),
  1508.               // subtractDate will only give us a non-zero number of hours on
  1509.               // DST changes. While strictly speaking, subtractDate's behavior
  1510.               // is correct, we need to move the event a discrete number of
  1511.               // days here. There is no need for normalization here, since
  1512.               // addDuration does the job for us. Also note, the duration used
  1513.               // here is only used to move over multiple days. Moving on the
  1514.               // same day uses the minutes from the dragState.
  1515.               if (duration.hours == 23) {
  1516.                   // entering DST
  1517.                   duration.hours++;
  1518.               } else if (duration.hours == 1) {
  1519.                   // leaving DST
  1520.                   duration.hours--;
  1521.               }
  1522.  
  1523.               if (duration.isNegative) {
  1524.                   // Adding negative minutes to a negative duration makes the
  1525.                   // duration more positive, but we want more negative, and
  1526.                   // vice versa.
  1527.                   minutes *= -1;
  1528.               }
  1529.               duration.minutes = minutes;
  1530.               duration.normalize();
  1531.  
  1532.               newStart.addDuration(duration);
  1533.               newEnd.addDuration(duration);
  1534.           }
  1535.  
  1536.           // If we tweaked tzs, put times back in their original ones
  1537.           if (startTZ) {
  1538.               newStart = newStart.getInTimezone(startTZ);
  1539.           }
  1540.           if (endTZ) {
  1541.               newEnd = newEnd.getInTimezone(endTZ);
  1542.           }
  1543.  
  1544.           if (dragState.dragType == "new") {
  1545.               col.mCreatedNewEvent = true;
  1546.               col.calendarView.controller.createNewEvent(col.calendarView.displayCalendar,
  1547.                                                          newStart,
  1548.                                                          newEnd);
  1549.           } else if (dragState.dragType == "move" ||
  1550.                      dragState.dragType == "modify-start" ||
  1551.                      dragState.dragType == "modify-end")
  1552.           {
  1553.               col.calendarView.controller.modifyOccurrence(dragState.dragOccurrence,
  1554.                                                            newStart, newEnd);
  1555.           }
  1556.           document.calendarEventColumnDragging = null;
  1557.           col.mDragState = null;
  1558.         ]]></body>
  1559.       </method>
  1560.  
  1561.       <!-- This is called by an event box when a grippy on either side is dragged,
  1562.          - or when the middle is pressed to drag the event to move it.  We create
  1563.          - the same type of view that we use to sweep out a new event, but we
  1564.          - initialize it based on the event's values and what type of dragging
  1565.          - we're doing.  In addition, we constrain things like not being able to
  1566.          - drag the end before the start and vice versa.
  1567.         -->
  1568.       <method name="startSweepingToModifyEvent">
  1569.         <parameter name="aEventBox"/>
  1570.         <parameter name="aOccurrence"/>
  1571.         <!-- "start", "end", "middle" -->
  1572.         <parameter name="aGrabbedElement"/>
  1573.         <!-- mouse screenX/screenY from the event -->
  1574.         <parameter name="aMouseX"/>
  1575.         <parameter name="aMouseY"/>
  1576.         <body><![CDATA[
  1577.           if (!isCalendarWritable(aOccurrence.calendar) ||
  1578.               aOccurrence.calendar.getProperty("capabilities.events.supported") === false) {
  1579.               return;
  1580.           }
  1581.  
  1582.           //dump ("startSweepingToModify\n");
  1583.           this.mDragState = {
  1584.               origColumn: this,
  1585.               dragOccurrence: aOccurrence,
  1586.               mouseOffset: 0
  1587.           };
  1588.  
  1589.           var interval = this.mPixPerMin * 15;
  1590.           var sizeattr;
  1591.  
  1592.           //dump ("AMY: " + aMouseY + " boY: " + this.parentNode.boxObject.screenY + "\n");
  1593.           var frameloc;
  1594.           if (this.getAttribute("orient") == "vertical") {
  1595.               this.mDragState.origLoc = aMouseY;
  1596.               frameloc = aMouseY - this.parentNode.boxObject.screenY;
  1597.               sizeattr = "height";
  1598.           } else {
  1599.               this.mDragState.origLoc = aMouseX;
  1600.               frameloc = aMouseX - this.parentNode.boxObject.screenX;
  1601.               sizeattr = "width";
  1602.           }
  1603.  
  1604.           var mins = this.getStartEndMinutesForOccurrence(aOccurrence);
  1605.  
  1606.           // these are only used to compute durations or to compute UI
  1607.           // sizes, so offset by this.mStartMin for sanity here (at the
  1608.           // expense of possible insanity later)
  1609.           mins.start -= this.mStartMin;
  1610.           mins.end -= this.mStartMin;
  1611.  
  1612.           if (aGrabbedElement == "start") {
  1613.               this.mDragState.dragType = "modify-start";
  1614.               this.mDragState.limitEndMin = mins.end;
  1615.  
  1616.               // snap start
  1617.               this.mDragState.origMin = Math.floor(mins.start/15) * 15;
  1618.               this.fgboxes.dragspacer.setAttribute(sizeattr, this.mDragState.origMin * this.mPixPerMin);
  1619.               this.fgboxes.dragbox.setAttribute(sizeattr, (mins.end - this.mDragState.origMin) * this.mPixPerMin);
  1620.           } else if (aGrabbedElement == "end") {
  1621.               this.mDragState.dragType =  "modify-end";
  1622.               this.mDragState.limitStartMin = mins.start;
  1623.  
  1624.               // snap end
  1625.               this.mDragState.origMin = Math.floor(mins.end/15) * 15;
  1626.               this.fgboxes.dragspacer.setAttribute(sizeattr, mins.start * this.mPixPerMin);
  1627.               this.fgboxes.dragbox.setAttribute(sizeattr, (this.mDragState.origMin - mins.start) * this.mPixPerMin);
  1628.           } else if (aGrabbedElement == "middle") {
  1629.               this.mDragState.dragType = "move";
  1630.               this.mDragState.limitDurationMin = mins.end - mins.start;
  1631.  
  1632.               // in a move, origMin will be the min of the start element;
  1633.               // so we snap start again, but we keep the duration the same
  1634.               // (we move the end based on the duration of the event,
  1635.               // not including our snap)
  1636.               this.mDragState.origMin = Math.floor(mins.start/15) * 15;
  1637.  
  1638.               // Because we can pass this event to other columns, we also need
  1639.               // to track the original column's date too, to get the correct offset
  1640.               this.mDragState.origDate = this.mDate;
  1641.               this.fgboxes.dragspacer.setAttribute(sizeattr, this.mDragState.origMin * this.mPixPerMin);
  1642.               this.fgboxes.dragbox.setAttribute(sizeattr, (mins.end - mins.start) * this.mPixPerMin);
  1643.  
  1644.               // we need to set a mouse offset, since we're not dragging from
  1645.               // one end of the element
  1646.               if (aEventBox) {
  1647.                   if (this.getAttribute("orient") == "vertical")
  1648.                       this.mDragState.mouseOffset = aMouseY - aEventBox.boxObject.screenY;
  1649.                   else
  1650.                       this.mDragState.mouseOffset = aMouseX - aEventBox.boxObject.screenX;
  1651.               }
  1652.           } else {
  1653.               dump ("+++ Invalid grabbed element: '" + aGrabbedElement + "'\n");
  1654.           }
  1655.  
  1656.           this.fgboxes.box.setAttribute("dragging", "true");
  1657.           this.fgboxes.dragbox.setAttribute("dragging", "true");
  1658.  
  1659.           document.calendarEventColumnDragging = this;
  1660.  
  1661.           //dump (">>> drag is: " + this.mDragState.dragType + "\n");
  1662.  
  1663.           window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
  1664.           window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
  1665.         ]]></body>
  1666.       </method>
  1667.  
  1668.       <!-- called by sibling columns to tell us to take over the sweeping
  1669.          - of an event.  Used by "move".
  1670.         -->
  1671.       <method name="acceptInProgressSweep">
  1672.         <parameter name="aDragState"/>
  1673.         <body><![CDATA[
  1674.           this.mDragState = aDragState;
  1675.           document.calendarEventColumnDragging = this;
  1676.  
  1677.           this.fgboxes.box.setAttribute("dragging", "true");
  1678.           this.fgboxes.dragbox.setAttribute("dragging", "true");
  1679.  
  1680.           // the same event handlers are still valid,
  1681.           // because they use document.calendarEventColumnDragging.
  1682.           // So we really don't have anything to do here.
  1683.         ]]></body>
  1684.       </method>
  1685.  
  1686.       <method name="updateDragLabels">
  1687.         <body><![CDATA[
  1688.           if (!this.mDragState) return;
  1689.  
  1690.           var realstartmin = this.mDragState.startMin + this.mStartMin;
  1691.           var realendmin = this.mDragState.endMin + this.mStartMin;
  1692.  
  1693.  
  1694.           if (this.mDragState.dragType == "move") {
  1695.               realendmin = realstartmin + this.mDragState.limitDurationMin;
  1696.           } else if (this.mDragState.dragType == "start") {
  1697.               realendmin = this.mDragState.limitEndMin;
  1698.           } else if (this.mDragState.dragType == "end") {
  1699.               realstartmin = this.mDragSTate.limitStartMin;
  1700.           }
  1701.  
  1702.           var starthr = Math.floor(realstartmin / 60);
  1703.           var startmin = realstartmin % 60;
  1704.  
  1705.           var endhr = Math.floor(realendmin / 60);
  1706.           var endmin = realendmin % 60;
  1707.  
  1708.           var formatter = Components.classes["@mozilla.org/intl/scriptabledateformat;1"].
  1709.                           getService(Components.interfaces.nsIScriptableDateFormat);
  1710.           var startstr = formatter.FormatTime("",
  1711.                                               Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
  1712.                                               starthr, startmin, 0);
  1713.           var endstr = formatter.FormatTime("",
  1714.                                             Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
  1715.                                             endhr, endmin, 0);
  1716.  
  1717.           this.fgboxes.startlabel.setAttribute("value", startstr);
  1718.           this.fgboxes.endlabel.setAttribute("value", endstr);
  1719.  
  1720.         ]]></body>
  1721.       </method>
  1722.       <method name="setDayStartEndMinutes">
  1723.         <parameter name="aDayStartMin"/>
  1724.         <parameter name="aDayEndMin"/>
  1725.         <body><![CDATA[
  1726.           if (aDayStartMin < this.mStartMin || aDayStartMin > aDayEndMin ||
  1727.             aDayEndMin > this.mEndMin) {
  1728.             throw Components.results.NS_ERROR_INVALID_ARG;
  1729.           }
  1730.           if (this.mDayStartMin != aDayStartMin ||
  1731.             this.mDayEndMin != aDayEndMin) {
  1732.             this.mDayStartMin = aDayStartMin;
  1733.             this.mDayEndMin = aDayEndMin;
  1734.           }
  1735.         ]]></body>
  1736.       </method>
  1737.     </implementation>
  1738.  
  1739.     <handlers>
  1740.       <handler event="dblclick" button="0"><![CDATA[
  1741.         if (this.calendarView.controller) {
  1742.             var newStart = this.date.clone();
  1743.             newStart.isDate = false;
  1744.             newStart.hour = 0;
  1745.  
  1746.             const ROUND_INTERVAL = 15;
  1747.  
  1748.             var interval = this.mPixPerMin * ROUND_INTERVAL;
  1749.             var pos;
  1750.             if (this.getAttribute("orient") == "vertical") {
  1751.                 pos = event.screenY - this.parentNode.boxObject.screenY;
  1752.             } else {
  1753.                 pos = event.screenX - this.parentNode.boxObject.screenX;
  1754.             }
  1755.             newStart.minute = (Math.round(pos/interval) * ROUND_INTERVAL) + this.mStartMin;
  1756.             this.calendarView.controller.createNewEvent(null, newStart, null);
  1757.         }
  1758.       ]]></handler>
  1759.  
  1760.       <handler event="click" button="0"><![CDATA[
  1761.         this.calendarView.setSelectedItems(0, []);
  1762.       ]]></handler>
  1763.  
  1764.       <!-- mouse down handler, in empty event column regions.  Starts sweeping out a new
  1765.          - event.
  1766.         -->
  1767.       <handler event="mousedown"><![CDATA[
  1768.         // select this column
  1769.         this.calendarView.selectedDay = this.mDate;
  1770.  
  1771.         // If the selected calendar is readOnly, we don't want any sweeping.
  1772.         var cal = getSelectedCalendar();
  1773.         if (!isCalendarWritable(cal) ||
  1774.             cal.getProperty("capabilities.events.supported") === false) {
  1775.             return;
  1776.         }
  1777.  
  1778.         // Only start sweeping out an event if the left button was clicked
  1779.         if (event.button != 0) {
  1780.             return;
  1781.         }
  1782.  
  1783.         // snap to 15 minute intervals
  1784.         var interval = this.mPixPerMin * 15;
  1785.  
  1786.         this.mDragState = {
  1787.             origColumn: this,
  1788.             dragType: "new",
  1789.             mouseOffset: 0
  1790.         };
  1791.  
  1792.         if (this.getAttribute("orient") == "vertical") {
  1793.             this.mDragState.origLoc = event.screenY;
  1794.             this.mDragState.origMin = Math.floor((event.screenY - this.parentNode.boxObject.screenY)/interval) * 15;
  1795.             this.fgboxes.dragspacer.setAttribute("height", this.mDragState.origMin * this.mPixPerMin);
  1796.         } else {
  1797.             this.mDragState.origLoc = event.screenX;
  1798.             this.mDragState.origMin = Math.floor((event.screenX - this.parentNode.boxObject.screenX)/interval) * 15;
  1799.             this.fgboxes.dragspacer.setAttribute("width", this.mDragState.origMin * this.mPixPerMin);
  1800.         }
  1801.  
  1802.         document.calendarEventColumnDragging = this;
  1803.  
  1804.         window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
  1805.         window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
  1806.       ]]></handler>
  1807.  
  1808.  
  1809.     </handlers>
  1810.   </binding>
  1811.  
  1812.   <binding id="calendar-header-container">
  1813.     <content>
  1814.       <xul:vbox xbl:inherits="selected" anonid="thebox" flex="1" class="calendar-event-column-header"/>
  1815.     </content>
  1816.  
  1817.     <implementation>
  1818.       <field name="mItemBoxes">null</field>
  1819.       <field name="mDate">null</field>
  1820.       <field name="mCalendarView">null</field>
  1821.  
  1822.       <constructor><![CDATA[
  1823.         this.mItemBoxes = new Array();
  1824.       ]]></constructor>
  1825.  
  1826.       <property name="mainbox">
  1827.         <getter><![CDATA[
  1828.           return document.getAnonymousElementByAttribute(this, 'anonid', 'thebox');
  1829.         ]]></getter>
  1830.       </property>
  1831.  
  1832.       <property name="date">
  1833.         <getter><![CDATA[
  1834.           return this.mDate;
  1835.         ]]></getter>
  1836.         <setter><![CDATA[
  1837.           this.mDate = val;
  1838.           return val;
  1839.         ]]></setter>
  1840.       </property>
  1841.  
  1842.       <property name="calendarView">
  1843.         <getter><![CDATA[
  1844.           return this.mCalendarView;
  1845.         ]]></getter>
  1846.         <setter><![CDATA[
  1847.           this.mCalendarView = val;
  1848.           return val;
  1849.         ]]></setter>
  1850.       </property>
  1851.  
  1852.       <method name="findBoxForItem">
  1853.         <parameter name="aItem"/>
  1854.         <body><![CDATA[
  1855.           for each (var item in this.mItemBoxes) {
  1856.               if (aItem && item.occurrence.hasSameIds(aItem)) {
  1857.                   // We can return directly, since there will only be one box per
  1858.                   // item in the header.
  1859.                   return item;
  1860.               }
  1861.           }
  1862.           return null;
  1863.         ]]></body>
  1864.       </method>
  1865.  
  1866.       <method name="addEvent">
  1867.         <parameter name="aItem"/>
  1868.         <body><![CDATA[
  1869.           // prevent same items being added
  1870.           if (this.mItemBoxes.some(function (itemBox) {
  1871.                                      return itemBox.occurrence.hashId == aItem.hashId;
  1872.                                    })) {
  1873.             return;
  1874.           }
  1875.  
  1876.           function createXULElement(el) {
  1877.             return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
  1878.           }
  1879.           var itemBox = createXULElement("calendar-editable-item");
  1880.  
  1881.           this.mainbox.appendChild(itemBox);
  1882.           itemBox.calendarView = this.calendarView;
  1883.           itemBox.occurrence = aItem;
  1884.           var ctxt = this.calendarView.getAttribute("item-context") ||
  1885.                      this.calendarView.getAttribute("context");
  1886.           itemBox.setAttribute("context", ctxt);
  1887.  
  1888.           if (aItem.hashId in this.calendarView.mFlashingEvents) {
  1889.             itemBox.setAttribute("flashing", "true");
  1890.           }
  1891.  
  1892.           this.mItemBoxes.push(itemBox);
  1893.         ]]></body>
  1894.       </method>
  1895.  
  1896.       <method name="deleteEvent">
  1897.         <parameter name="aItem"/>
  1898.         <body><![CDATA[
  1899.           for (var i in this.mItemBoxes) {
  1900.             if (this.mItemBoxes[i].occurrence.hashId == aItem.hashId) {
  1901.               this.mainbox.removeChild(this.mItemBoxes[i]);
  1902.               this.mItemBoxes.splice(i, 1);
  1903.               break;
  1904.             }
  1905.           }
  1906.         ]]></body>
  1907.       </method>
  1908.  
  1909.       <method name="selectOccurrence">
  1910.         <parameter name="aItem"/>
  1911.         <body><![CDATA[
  1912.           for each (itemBox in this.mItemBoxes) {
  1913.             if (aItem && (itemBox.occurrence.hashId == aItem.hashId)) {
  1914.               itemBox.selected = true;
  1915.             }
  1916.  
  1917.           }
  1918.         ]]></body>
  1919.       </method>
  1920.       <method name="unselectOccurrence">
  1921.         <parameter name="aItem"/>
  1922.         <body><![CDATA[
  1923.           for each (itemBox in this.mItemBoxes) {
  1924.             if (aItem && (itemBox.occurrence.hashId == aItem.hashId)) {
  1925.               itemBox.selected = false;
  1926.             }
  1927.           }
  1928.         ]]></body>
  1929.       </method>
  1930.  
  1931.     </implementation>
  1932.  
  1933.     <handlers>
  1934.       <handler event="dblclick" button="0"><![CDATA[
  1935.         this.calendarView.controller.createNewEvent();
  1936.       ]]></handler>
  1937.       <handler event="mousedown"><![CDATA[
  1938.         this.calendarView.selectedDay = this.mDate;
  1939.       ]]></handler>
  1940.       <handler event="click" button="0"><![CDATA[
  1941.         this.calendarView.setSelectedItems(0, []);
  1942.       ]]></handler>
  1943.     </handlers>
  1944.   </binding>
  1945.  
  1946.   <!--
  1947.      -  An individual event box, to be inserted into a column.
  1948.     -->
  1949.   <binding id="calendar-event-box" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
  1950.  
  1951.     <content>
  1952.       <xul:box anonid="shadowbox"
  1953.                xbl:inherits="width,height">
  1954.         <xul:box anonid="shadow-left-box"
  1955.                  xbl:inherits="orient" >
  1956.           <xul:image anonid="shadow-left"
  1957.                      class="calendar-event-box-shadow-left"
  1958.                      flex="1"/>
  1959.           <xul:image anonid="shadow-edge-left"
  1960.                      class="calendar-event-box-shadow-edge-left"/>
  1961.         </xul:box>
  1962.         <xul:box xbl:inherits="orient,width,height" flex="1">
  1963.           <xul:image anonid="shadow-left-if-vertical"
  1964.                      class="calendar-event-box-shadow-left"
  1965.                      hidden="true"/>
  1966.           <xul:box anonid="event-container" xbl:inherits="orient" flex="1">
  1967.             <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
  1968.               <xul:stack anonid="eventbox"
  1969.                          align="stretch"
  1970.                          class="calendar-event-box-container"
  1971.                          flex="1"
  1972.                          xbl:inherits="context,parentorient=orient">
  1973.                 <xul:vbox class="calendar-event-details">
  1974.                   <xul:description anonid="event-name" class="calendar-event-details-core" flex="1"/>
  1975.                   <xul:textbox anonid="event-name-textbox"
  1976.                                class="plain calendar-event-details-core"
  1977.                                flex="1"
  1978.                                hidden="true"
  1979.                                style="background: transparent !important;"
  1980.                                wrap="true"/>
  1981.                 </xul:vbox>
  1982.                 <xul:image width="1px" class="calendar-event-box-gradient"/>
  1983.                 <xul:hbox pack="end">
  1984.                     <xul:vbox pack="start">
  1985.                       <xul:image anonid="alarm-image"
  1986.                                  class="alarm-image"
  1987.                                  xbl:inherits="flashing"
  1988.                                  hidden="true"/>
  1989.                     </xul:vbox>
  1990.                 </xul:hbox>
  1991.                 <xul:box xbl:inherits="orient" flex="1">
  1992.                   <xul:calendar-event-gripbar anonid="gripbar1"
  1993.                                               class="calendar-event-box-grippy-top"
  1994.                                               whichside="start"
  1995.                                               xbl:inherits="parentorient=orient"/>
  1996.                   <xul:spacer flex="1"/>
  1997.                   <xul:calendar-event-gripbar anonid="gripbar2"
  1998.                                               class="calendar-event-box-grippy-bottom"
  1999.                                               whichside="end"
  2000.                                               xbl:inherits="parentorient=orient"/>
  2001.                 </xul:box>
  2002.                 <!-- Do not insert anything here, otherwise the event boxes will
  2003.                      not be resizable using the gripbars. If you want to insert
  2004.                      additional elements, do so above the box with the gripbars. -->
  2005.               </xul:stack>
  2006.               <xul:calendar-category-box anonid="category-box"/>
  2007.             </xul:box>
  2008.           </xul:box>
  2009.           <xul:image anonid="shadow-bottom"
  2010.                      class="calendar-event-box-shadow-bottom"/>
  2011.         </xul:box>
  2012.         <xul:box xbl:inherits="orient">
  2013.           <xul:image anonid="shadow-edge-left-if-vertical"
  2014.                      class="calendar-event-box-shadow-edge-left"
  2015.                      hidden="true"/>
  2016.           <xul:image anonid="shadow-right"
  2017.                      class="calendar-event-box-shadow-right"
  2018.                      flex="1"/>
  2019.           <xul:image anonid="shadow-edge-right"
  2020.                      class="calendar-event-box-shadow-edge-right"/>
  2021.         </xul:box>
  2022.       </xul:box>
  2023.     </content>
  2024.  
  2025.     <implementation>
  2026.       <constructor><![CDATA[
  2027.          this.orient = this.getAttribute("orient");
  2028.          var otherorient = "vertical";
  2029.          if (!orient) orient = "horizontal";
  2030.          if (orient == "vertical") otherorient = "horizontal";
  2031.  
  2032.          var shadowbox = document.getAnonymousElementByAttribute(this, "anonid", "shadowbox");
  2033.          shadowbox.setAttribute("orient",otherorient);
  2034.  
  2035.          if (orient != "vertical") {
  2036.              // Switch bottom/right shadows
  2037.              var shadowBottom = document.getAnonymousElementByAttribute(this, "anonid", "shadow-bottom");
  2038.              var shadowRight = document.getAnonymousElementByAttribute(this, "anonid", "shadow-right");
  2039.              shadowBottom.setAttribute("class", "calendar-event-box-shadow-right");
  2040.              shadowRight.setAttribute("class", "calendar-event-box-shadow-bottom");
  2041.  
  2042.              // Move left shadow in layout
  2043.              document.getAnonymousElementByAttribute(this, "anonid", "shadow-edge-left-if-vertical").removeAttribute("hidden");
  2044.              document.getAnonymousElementByAttribute(this, "anonid", "shadow-left-if-vertical").removeAttribute("hidden");
  2045.              document.getAnonymousElementByAttribute(this, "anonid", "shadow-left-box").setAttribute("hidden","true");
  2046.          }
  2047.       ]]></constructor>
  2048.  
  2049.       <!-- fields -->
  2050.       <field name="mParentColumn">null</field>
  2051.  
  2052.       <!-- methods/properties -->
  2053.       <method name="setAttribute">
  2054.         <parameter name="aAttr"/>
  2055.         <parameter name="aVal"/>
  2056.         <body><![CDATA[
  2057.           var needsrelayout = false;
  2058.           if (aAttr == "orient") {
  2059.               if (this.getAttribute("orient") != aVal)
  2060.                   needsrelayout = true;
  2061.           }
  2062.  
  2063.           // this should be done using lookupMethod(), see bug 286629
  2064.           var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
  2065.  
  2066.           if (needsrelayout) {
  2067.               var otherorient = "vertical";
  2068.               if (val != "horizontal") otherorient = "horizontal";
  2069.  
  2070.               var eventbox = document.getAnonymousElementByAttribute(this, "anonid", "eventbox");
  2071.               eventbox.setAttribute("orient", val);
  2072.               eventbox.setAttribute("class", "calendar-item calendar-event-box-" + val);
  2073.               var gb1 = document.getAnonymousElementByAttribute(this, "anonid", "gripbar1");
  2074.               gb1.parentorient = val;
  2075.               var gb2 = document.getAnonymousElementByAttribute(this, "anonid", "gripbar2");
  2076.               gb2.parentorient = val;
  2077.           }
  2078.  
  2079.           return ret;
  2080.         ]]></body>
  2081.       </method>
  2082.  
  2083.       <property name="parentColumn"
  2084.         onget="return this.mParentColumn;"
  2085.         onset="return (this.mParentColumn = val);"/>
  2086.  
  2087.       <property name="startMinute">
  2088.         <getter><![CDATA[
  2089.             if (!this.mOccurrence)
  2090.                 return 0;
  2091.             var startDate = this.mOccurrence.startDate || this.mOccurrence.entryDate;
  2092.             return startDate.hour * 60 + startDate.minute;
  2093.         ]]></getter>
  2094.       </property>
  2095.  
  2096.       <property name="endMinute">
  2097.         <getter><![CDATA[
  2098.             if (!this.mOccurrence)
  2099.                 return 0;
  2100.             var endDate = this.mOccurrence.endDate || this.mOccurrence.dueDate;
  2101.             return endDate.hour * 60 + endDate.minute;
  2102.         ]]></getter>
  2103.       </property>
  2104.  
  2105.       <method name="setEditableLabel">
  2106.         <body><![CDATA[
  2107.           var evl = this.eventNameLabel;
  2108.           var item = this.mOccurrence;
  2109.  
  2110.           if (item.title && item.title != "") {
  2111.             // Use <description> textContent so it can wrap.
  2112.             evl.textContent = item.title;
  2113.           } else {
  2114.             evl.textContent = calGetString("calendar", "eventUntitled");
  2115.           }
  2116.  
  2117.           var gripbar = document.getAnonymousElementByAttribute(this, "anonid", "gripbar1").boxObject.height;
  2118.           var height = document.getAnonymousElementByAttribute(this, "anonid", "eventbox").boxObject.height;
  2119.           evl.setAttribute("style", "max-height: " + (height-gripbar*2) + "px");
  2120.         ]]></body>
  2121.       </method>
  2122.     </implementation>
  2123.  
  2124.     <handlers>
  2125.       <handler event="mousedown" button="0"><![CDATA[
  2126.         event.stopPropagation();
  2127.  
  2128.         if (this.mEditing)
  2129.             return;
  2130.  
  2131.         this.parentColumn.calendarView.selectedDay = this.parentColumn.mDate;
  2132.         this.mInMouseDown = true;
  2133.         this.mMouseX = event.screenX;
  2134.         this.mMouseY = event.screenY;
  2135.         
  2136.         var whichside = event.whichside;
  2137.         if (!whichside)
  2138.             return;
  2139.         
  2140.         this.calendarView.setSelectedItems(1,
  2141.             [event.ctrlKey ? this.mOccurrence.parentItem : this.mOccurrence]);
  2142.  
  2143.         // start dragging it
  2144.         this.parentColumn.startSweepingToModifyEvent(this, this.mOccurrence, whichside, event.screenX, event.screenY);
  2145.       ]]></handler>
  2146.  
  2147.       <handler event="mousemove"><![CDATA[
  2148.         if (!this.mInMouseDown)
  2149.             return;
  2150.         var dx = Math.abs(event.screenX - this.mMouseX);
  2151.         var dy = Math.abs(event.screenY - this.mMouseY);
  2152.         // more than a 3 pixel move?
  2153.         if ((dx*dx + dy*dy) > 9) {
  2154.             if (this.parentColumn) {
  2155.                 if (this.editingTimer) {
  2156.                     clearTimeout(this.editingTimer);
  2157.                     this.editingTimer = null;
  2158.                 }
  2159.  
  2160.                 this.calendarView.selectedItem = this.mOccurrence;
  2161.  
  2162.                 this.mEditing = false;
  2163.                 if (this.calendarView)
  2164.                     this.calendarView.activeInPlaceEdit = false;
  2165.  
  2166.                 this.parentColumn.startSweepingToModifyEvent(this, this.mOccurrence, "middle", this.mMouseX, this.mMouseY);
  2167.                 this.mInMouseDown = false;
  2168.             }
  2169.         }
  2170.       ]]></handler>
  2171.  
  2172.       <handler event="mouseup"><![CDATA[
  2173.         if (this.mEditing)
  2174.             return;
  2175.  
  2176.         this.mInMouseDown = false;
  2177.       ]]></handler>
  2178.  
  2179.       <handler event="mouseover"><![CDATA[
  2180.         if (this.calendarView && this.calendarView.controller) {
  2181.             event.stopPropagation();
  2182.             onMouseOverItem(event);
  2183.         }
  2184.       ]]></handler>
  2185.     </handlers>
  2186.   </binding>
  2187.  
  2188.   <binding id="calendar-day-label">
  2189.     <content>
  2190.       <xul:hbox anonid="mainbox" flex="1">
  2191.         <xul:spacer flex="1"/>
  2192.         <xul:vbox>
  2193.           <xul:label anonid="dateName" class="calendar-day-label-date"/>
  2194.           <xul:label anonid="longWeekdayName" class="calendar-day-label-name"/>
  2195.           <xul:label anonid="shortWeekdayName" class="calendar-day-label-name" hidden="true"/>
  2196.         </xul:vbox>
  2197.         <xul:spacer flex="1"/>
  2198.       </xul:hbox>
  2199.     </content>
  2200.     <implementation>
  2201.       <field name="longWeekdayPixels">0</field>
  2202.       <field name="mDate">null</field>
  2203.  
  2204.       <property name="date">
  2205.         <getter><![CDATA[
  2206.           return this.mDate;
  2207.         ]]></getter>
  2208.         <setter><![CDATA[
  2209.           this.mDate = val;
  2210.           var df = Components.classes["@mozilla.org/calendar/datetime-formatter;1"].
  2211.                      getService(Components.interfaces.calIDateTimeFormatter);
  2212.  
  2213.           var shortName = document.getAnonymousElementByAttribute(this, "anonid", "shortWeekdayName");
  2214.           var longName = document.getAnonymousElementByAttribute(this, "anonid", "longWeekdayName");
  2215.           var day = document.getAnonymousElementByAttribute(this, "anonid", "dateName");
  2216.  
  2217.           longName.setAttribute("value", calGetString("dateFormat","day." + (val.weekday + 1) + ".name"));
  2218.           shortName.setAttribute("value", calGetString("dateFormat","day." + (val.weekday + 1) + ".Mmm"));
  2219.           day.setAttribute("value", df.formatDateWithoutYear(val));
  2220.  
  2221.           return val;
  2222.         ]]></setter>
  2223.       </property>
  2224.  
  2225.       <property name="shortWeekNames">
  2226.         <getter><![CDATA[
  2227.           return document.getAnonymousElementByAttribute(this, "anonid", "shortWeekdayName").hasAttribute("hidden");
  2228.         ]]></getter>
  2229.         <setter><![CDATA[
  2230.           var shortName = document.getAnonymousElementByAttribute(this, "anonid", "shortWeekdayName");
  2231.           var longName = document.getAnonymousElementByAttribute(this, "anonid", "longWeekdayName");
  2232.  
  2233.           // cache before change, in case we are switching to short
  2234.           this.cacheLongWeekdayPixels(longName);
  2235.  
  2236.           if (val) {
  2237.             longName.setAttribute("hidden","true");
  2238.             shortName.removeAttribute("hidden");
  2239.           } else {
  2240.             longName.removeAttribute("hidden");
  2241.             shortName.setAttribute("hidden","true");
  2242.           }
  2243.  
  2244.           // cache after change, in case we are switching from short
  2245.           this.cacheLongWeekdayPixels(longName);
  2246.  
  2247.           return val;
  2248.         ]]></setter>
  2249.       </property>
  2250.  
  2251.       <method name="cacheLongWeekdayPixels">
  2252.         <parameter name="longName"/>
  2253.         <body><![CDATA[
  2254.           // Only do this if the long weekdays are visible and we haven't already cached.
  2255.           if (!this.longWeekdayPixels && !longName.hasAttribute("hidden")) {
  2256.             this.longWeekdayPixels = longName.boxObject.width +
  2257.                                      parseInt(document.defaultView.getComputedStyle(longName, null).getPropertyValue("margin-left")) +
  2258.                                      parseInt(document.defaultView.getComputedStyle(longName, null).getPropertyValue("margin-right"));
  2259.           }
  2260.         ]]></body>
  2261.       </method>
  2262.     </implementation>
  2263.   </binding>
  2264.  
  2265.   <binding id="calendar-multiday-view">
  2266.     <content>
  2267.       <xul:box anonid="mainbox" flex="1" onoverflow="adjustWeekdayLength();">
  2268.         <!-- these boxes are tricky: width or height in CSS depend on orient -->
  2269.         <xul:box anonid="labelbox">
  2270.           <xul:box anonid="labeltimespacer"/>
  2271.           <xul:box anonid="labeldaybox" class="calendar-label-day-box" flex="1"
  2272.                    equalsize="always" />
  2273.           <xul:box anonid="labelscrollbarspacer"/>
  2274.         </xul:box>
  2275.         <xul:box anonid="headerbox">
  2276.           <xul:box anonid="headertimespacer"
  2277.                    class="calendar-header-time-spacer"/>
  2278.           <xul:box anonid="headerdaybox" class="calendar-header-day-box"
  2279.                    flex="1" equalsize="always" />
  2280.           <xul:box anonid="headerscrollbarspacer"/>
  2281.         </xul:box>
  2282.         <xul:scrollbox anonid="childbox" flex="1">
  2283.           <!-- the orient of the calendar-time-bar needs to be the opposite of the parent -->
  2284.           <xul:calendar-time-bar xbl:inherits="orient" anonid="timebar"/>
  2285.           <xul:box anonid="daybox" class="calendar-day-box" flex="1"
  2286.                    equalsize="always"/>
  2287.         </xul:scrollbox>
  2288.       </xul:box>
  2289.     </content>
  2290.  
  2291.     <implementation implements="calICalendarView">
  2292.  
  2293.       <field name="mResizeHandler">null</field>
  2294.       <constructor><![CDATA[
  2295.         var self = this;
  2296.         this.mTimezone = UTC();
  2297.         this.mResizeHandler = function resizeHandler() { self.onResize(); };
  2298.         window.addEventListener("resize", this.mResizeHandler, true);
  2299.         this.mScrollHandler = function scrollHandler() { self.onScroll(); };
  2300.         window.addEventListener("scroll", this.mScrollHandler, true);
  2301.  
  2302.         // set the flex attribute at the scrollbox-innerbox
  2303.         // (this can be removed, after Bug 343555 is fixed)
  2304.         var childbox = document.getAnonymousElementByAttribute(
  2305.                        this, "anonid", "childbox");
  2306.         document.getAnonymousElementByAttribute(
  2307.             childbox, "class", "box-inherit scrollbox-innerbox").flex = "1";
  2308.  
  2309.         this.reorient();
  2310.  
  2311.         var alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
  2312.                            .getService(Components.interfaces.calIAlarmService);
  2313.         alarmService.addObserver(this.mObserver);
  2314.       ]]></constructor>
  2315.  
  2316.       <destructor><![CDATA[
  2317.         if (this.mCalendar) {
  2318.             this.mCalendar.removeObserver(this.mObserver);
  2319.         }
  2320.         window.removeEventListener("scroll", this.mScrollHandler, true);
  2321.         window.removeEventListener("resize", this.mResizeHandler, true);
  2322.  
  2323.         var alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
  2324.                            .getService(Components.interfaces.calIAlarmService);
  2325.         alarmService.removeObserver(this.mObserver);
  2326.       ]]></destructor>
  2327.  
  2328.       <property name="daysInView" readonly="true">
  2329.         <getter><![CDATA[
  2330.           var labeldaybox = document.getAnonymousElementByAttribute(this, "anonid", "labeldaybox");
  2331.           return labeldaybox.childNodes && labeldaybox.childNodes.length;
  2332.         ]]></getter>
  2333.       </property>
  2334.  
  2335.       <method name="onResize">
  2336.         <parameter name="aRealSelf"/>
  2337.         <body><![CDATA[
  2338.           var self = this;
  2339.           if (aRealSelf) {
  2340.             self = aRealSelf;
  2341.           }
  2342.           var childbox = document.getAnonymousElementByAttribute(self, "anonid", "childbox");
  2343.           var size;
  2344.           if (self.orient == "horizontal") {
  2345.             size = childbox.boxObject.width;
  2346.           } else {
  2347.             size = childbox.boxObject.height;
  2348.           }
  2349.           var ppm = size / self.mVisibleMinutes;
  2350.           ppm = Math.floor(ppm * 1000) / 1000;
  2351.           if (ppm < self.mMinPixelsPerMinute) {
  2352.             ppm = self.mMinPixelsPerMinute;
  2353.           }
  2354.           self.pixelsPerMinute = ppm;
  2355.           setTimeout(function(){self.scrollToMinute(self.mFirstVisibleMinute)}, 1);
  2356.  
  2357.           // Fit the weekday labels while scrolling.
  2358.           self.adjustWeekdayLength();
  2359.         ]]></body>
  2360.       </method>
  2361.  
  2362.       <field name="mScrollHandler">null</field>
  2363.       <method name="onScroll">
  2364.         <body><![CDATA[
  2365.           // workaround: deck changes shouldn't provoke onScroll events...
  2366.           var displayDeck = document.getElementById("displayDeck");
  2367.           if (displayDeck && displayDeck.selectedPanel.id != "calendar-view-box") {
  2368.             return;
  2369.           }
  2370.           var panel = this.parentNode.parentNode.parentNode;
  2371.           var deck = panel.parentNode;
  2372.           if (panel.id != deck.selectedPanel.id) {
  2373.             return;
  2374.           }
  2375.           if (deck.style.visibility == "collapse") {
  2376.             return;
  2377.           }
  2378.           this.mFirstVisibleMinute = this.getFirstVisibleMinute();
  2379.         ]]></body>
  2380.       </method>
  2381.  
  2382.       <field name="mController">null</field>
  2383.       <field name="mCalendar">null</field>
  2384.       <field name="mStartDate">null</field>
  2385.       <field name="mEndDate">null</field>
  2386.       <!-- mDateList will always be sorted before being set -->
  2387.       <field name="mDateList">null</field>
  2388.       <!-- array of { date: calIDatetime, column: colbox, header: hdrbox }  -->
  2389.       <field name="mDateColumns">null</field>
  2390.       <field name="mBatchCount">0</field>
  2391.       <field name="mPixPerMin">0.6</field>
  2392.       <field name="mMinPixelsPerMinute">0.1</field>
  2393.       <field name="mLongWeekdayTotalPixels">0</field>
  2394.       <field name="mSelectedItems">[]</field>
  2395.       <field name="mSelectedDayCol">null</field>
  2396.       <field name="mSelectedDay">null</field>
  2397.  
  2398.       <field name="mStartMin">0*60</field>
  2399.       <field name="mEndMin">24*60</field>
  2400.       <field name="mDayStartMin">0</field>
  2401.       <field name="mDayEndMin">0</field>
  2402.       <field name="mVisibleMinutes">9*60</field>
  2403.       <field name="mTasksInView">false</field>
  2404.       <field name="mShowCompleted">false</field>
  2405.       <field name="mDisplayDaysOff">true</field>
  2406.       <field name="mDaysOffArray">[0,6]</field>
  2407.       <field name="mTimezone">null</field>
  2408.       <field name="mFlashingEvents">new Object()</field>
  2409.  
  2410.       <field name="mObserver"><![CDATA[
  2411.         // the calIObserver, calICompositeObserver, and calIAlarmServiceObserver
  2412.         ({
  2413.             QueryInterface: function QueryInterface(aIID) {
  2414.                 if (!aIID.equals(Components.interfaces.calIObserver) &&
  2415.                     !aIID.equals(Components.interfaces.calIAlarmServiceObserver) &&
  2416.                     !aIID.equals(Components.interfaces.calICompositeObserver) &&
  2417.                     !aIID.equals(Components.interfaces.nsISupports)) {
  2418.                     throw Components.results.NS_ERROR_NO_INTERFACE;
  2419.                 }
  2420.  
  2421.                 return this;
  2422.             },
  2423.  
  2424.             calView: this,
  2425.  
  2426.             onStartBatch: function onStartBatch() {
  2427.                 this.calView.mBatchCount++;
  2428.             },
  2429.             onEndBatch: function onEndBatch() {
  2430.                 this.mBatchCount--;
  2431.                 if (this.mBatchCount == 0) {
  2432.                     this.calView.refresh();
  2433.                 }
  2434.             },
  2435.             onLoad: function onLoad() {
  2436.                 this.calView.refresh();
  2437.             },
  2438.             onAddItem: function onAddItem(aItem) {
  2439.                 if (this.mBatchCount) {
  2440.                     return;
  2441.                 }
  2442.  
  2443.                 if (isToDo(aItem)) {
  2444.                     if (!aItem.entryDate || !aItem.dueDate) {
  2445.                         return;
  2446.                     }
  2447.                     if(!this.calView.mTasksInView){
  2448.                         return;
  2449.                     }
  2450.                     if (aItem.isCompleted && !this.calView.mShowCompleted) {
  2451.                         return;
  2452.                     }
  2453.                 }
  2454.  
  2455.                 var occs = aItem.getOccurrencesBetween(this.calView.startDate,
  2456.                                                        this.calView.queryEndDate,
  2457.                                                        {});
  2458.                 //dump ("occs: " + occs.length + "\n");
  2459.                 for each (var occ in occs) {
  2460.                     this.calView.doAddEvent(occ);
  2461.                 }
  2462.  
  2463.                 return;
  2464.             },
  2465.             onModifyItem: function onModifyItem(aNewItem, aOldItem) {
  2466.                 if (this.mBatchCount) {
  2467.                     return;
  2468.                 }
  2469.  
  2470.                 if (isToDo(aNewItem) && isToDo(aOldItem) &&
  2471.                     !this.calView.mTasksInView)
  2472.                     return;
  2473.  
  2474.                 var occs;
  2475.  
  2476.                 if (!isToDo(aOldItem) ||
  2477.                     (aOldItem.entryDate && aOldItem.dueDate)) {
  2478.                     occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
  2479.                                                           this.calView.queryEndDate,
  2480.                                                           {});
  2481.                     for each (var occ in occs) {
  2482.                         this.calView.doDeleteEvent(occ);
  2483.                     }
  2484.                 }
  2485.                 if (isToDo(aNewItem)) {
  2486.                     if (!aNewItem.entryDate || !aNewItem.dueDate || !this.calView.mTasksInView) {
  2487.                         return;
  2488.                     }
  2489.                     if (aNewItem.isCompleted && !this.calView.mShowCompleted) {
  2490.                         return;
  2491.                     }
  2492.                 }
  2493.  
  2494.                 occs = aNewItem.getOccurrencesBetween(this.calView.startDate,
  2495.                                                       this.calView.queryEndDate,
  2496.                                                       {});
  2497.                 for each (var occ in occs) {
  2498.                     this.calView.doAddEvent(occ);
  2499.                 }
  2500.             },
  2501.             onDeleteItem: function onDeleteItem(aItem) {
  2502.                 if (this.mBatchCount) {
  2503.                     return;
  2504.                 }
  2505.  
  2506.                 if (isToDo(aItem)) {
  2507.                     if (!this.calView.mTasksInView) {
  2508.                         return;
  2509.                     }
  2510.                     if (!aItem.entryDate || !aItem.dueDate) {
  2511.                         return;
  2512.                     }
  2513.                     if (aItem.isCompleted && !this.calView.mShowCompleted) {
  2514.                         return;
  2515.                     }
  2516.                 }
  2517.  
  2518.                 var occs = aItem.getOccurrencesBetween(this.calView.startDate,
  2519.                                                        this.calView.queryEndDate,
  2520.                                                        {});
  2521.                 for each (var occ in occs) {
  2522.                     this.calView.doDeleteEvent(occ);
  2523.                 }
  2524.             },
  2525.             onError: function onError(aErrNo, aMessage) { },
  2526.  
  2527.             onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
  2528.                 switch (aName) {
  2529.                     case "suppressAlarms":
  2530.                         if (!getPrefSafe("calendar.alarms.indicator.show", true) ||
  2531.                             aCalendar.getProperty("capabilities.alarms.popup.supported") === false ) {
  2532.                             break;
  2533.                         }
  2534.                         // else fall through
  2535.                     case "readOnly":
  2536.                         // XXXvv we can be smarter about how we handle this stuff
  2537.                         this.calView.refresh();
  2538.                         break;
  2539.                 }
  2540.             },
  2541.  
  2542.             onPropertyDeleting: function(aCalendar, aName) {
  2543.                 // Values are not important here yet.
  2544.                 this.onPropertyChanged(aCalendar, aName, null, null);
  2545.             },
  2546.  
  2547.             //
  2548.             // calIAlarmServiceObserver stuff
  2549.             //
  2550.             onAlarm: function onAlarm(aAlarmItem) {
  2551.                 this.calView.flashAlarm(aAlarmItem, false);
  2552.             },
  2553.  
  2554.             onRemoveAlarmsByItem: function onRemoveAlarmsByItem(aItem) {
  2555.                 // Stop the flashing for the item.
  2556.                 this.calView.flashAlarm(aItem, true);
  2557.             },
  2558.  
  2559.             onRemoveAlarmsByCalendar: function onRemoveAlarmsByCalendar(aCalendar) {
  2560.                 // Stop the flashing for all items of this calendar
  2561.                 for each (var item in this.calView.mFlashingEvents) {
  2562.                     if (item.calendar.id == aCalendar.id) {
  2563.                         this.calView.flashAlarm(item, true);
  2564.                     }
  2565.                 }
  2566.             },
  2567.  
  2568.             //
  2569.             // calICompositeObserver stuff
  2570.             // XXXvv we can be smarter about how we handle this stuff
  2571.             //
  2572.             onCalendarAdded: function onCalendarAdded(aCalendar) {
  2573.                 //dump ("view onCalendarAdded\n");
  2574.                 this.calView.refresh();
  2575.             },
  2576.  
  2577.             onCalendarRemoved: function onCalendarRemoved(aCalendar) {
  2578.                 //dump ("view onCalendarRemoved\n");
  2579.                 this.calView.refresh();
  2580.             },
  2581.  
  2582.             onDefaultCalendarChanged:
  2583.             function onDefaultCalendarChanged(aNewDefaultCalendar) {
  2584.                 // don't care, for now
  2585.             }
  2586.         })
  2587.       ]]></field>
  2588.  
  2589.       <field name="mOperationListener"><![CDATA[
  2590.       ({
  2591.           calView: this,
  2592.  
  2593.           onOperationComplete:
  2594.           function onOperationComplete(aCalendar, aStatus, aOperationType,
  2595.                                        aId, aDetail) {
  2596.               // Fire event
  2597.               this.calView.fireEvent('viewloaded', aOperationType);
  2598.  
  2599.               // signal that the current operation finished.
  2600.               this.calView.mRefreshPending = null;
  2601.  
  2602.               // immediately start the next job on the queue.
  2603.               this.calView.popRefreshQueue();
  2604.           },
  2605.           onGetResult:
  2606.           function onGetResult(aCalendar, aStatus, aItemType, aDetail,
  2607.                                aCount, aItems) {
  2608.               if (!Components.isSuccessCode(aStatus))
  2609.                   return;
  2610.  
  2611.               function hasGoodDates(item) {
  2612.                   if (isToDo(item) && (!item.entryDate || !item.dueDate)) {
  2613.                       return false;
  2614.                   }
  2615.                   return true;
  2616.               }
  2617.               aItems = aItems.filter(hasGoodDates);
  2618.  
  2619.               for each (var item in aItems) {
  2620.                   this.calView.doAddEvent(item);
  2621.               }
  2622.           }
  2623.       })
  2624.       ]]></field>
  2625.  
  2626.       <method name="flashAlarm">
  2627.         <parameter name="aAlarmItem"/>
  2628.         <parameter name="aStop"/>
  2629.         <body><![CDATA[
  2630.           var showIndicator = getPrefSafe("calendar.alarms.indicator.show", true);
  2631.           var totaltime = getPrefSafe("calendar.alarms.indicator.totaltime", 3600);
  2632.  
  2633.           if (!aStop && (!showIndicator || totaltime < 1)) {
  2634.             // No need to animate if the indicator should not be shown.
  2635.             return;
  2636.           }
  2637.  
  2638.           // Helper function to save some duplicate code
  2639.           function setFlashingAttribute(aBox) {
  2640.             if (aStop) {
  2641.               aBox.removeAttribute("flashing");
  2642.             } else {
  2643.               aBox.setAttribute("flashing", "true");
  2644.             }
  2645.           }
  2646.  
  2647.           // Make sure the flashing attribute is set or reset on all visible
  2648.           // boxes.
  2649.           var columns = this.findColumnsForItem(aAlarmItem);
  2650.           for each (var col in columns) {
  2651.             var box = col.column.findChunkForOccurrence(aAlarmItem);
  2652.             if (box && box.eventbox) {
  2653.               setFlashingAttribute(box.eventbox);
  2654.             }
  2655.             box = col.header.findBoxForItem(aAlarmItem);
  2656.             if (box) {
  2657.               setFlashingAttribute(box);
  2658.             }
  2659.           }
  2660.  
  2661.           if (!aStop) {
  2662.             // Set up a timer to stop the flashing after the total time.
  2663.             var this_ = this;
  2664.             this.mFlashingEvents[aAlarmItem.hashId] = aAlarmItem;
  2665.             setTimeout(function() { this_.flashAlarm(aAlarmItem, true) }, totaltime);
  2666.           } else {
  2667.             // We are done flashing, prevent newly created event boxes from flashing.
  2668.             delete this.mFlashingEvents[aAlarmItem.hashId];
  2669.           }
  2670.         ]]></body>
  2671.       </method>
  2672.  
  2673.       <!-- calICalendarView -->
  2674.       <property name="supportsDisjointDates"
  2675.         onget="return true"/>
  2676.       <property name="hasDisjointDates"
  2677.         onget="return (this.mDateList != null);"/>
  2678.  
  2679.       <property name="controller"
  2680.         onget="return this.mController;"
  2681.         onset="return (this.mController = val);" />
  2682.  
  2683.       <property name="displayCalendar">
  2684.         <getter><![CDATA[
  2685.           return this.mCalendar;
  2686.         ]]></getter>
  2687.         <setter><![CDATA[
  2688.           if (this.mCalendar)
  2689.               this.mCalendar.removeObserver (this.mObserver);
  2690.           this.mCalendar = val;
  2691.           this.mCalendar.addObserver (this.mObserver);
  2692.  
  2693.           this.refresh();
  2694.           return val;
  2695.         ]]></setter>
  2696.       </property>
  2697.  
  2698.       <property name="startDate">
  2699.         <getter><![CDATA[
  2700.           if (this.mStartDate) return this.mStartDate;
  2701.           else if (this.mDateList && this.mDateList.length > 0) return this.mDateList[0];
  2702.           else return null;
  2703.         ]]></getter>
  2704.       </property>
  2705.  
  2706.       <property name="endDate">
  2707.         <getter><![CDATA[
  2708.           if (this.mEndDate) return this.mEndDate;
  2709.           else if (this.mDateList && this.mDateList.length > 0) return this.mDateList[this.mDateList.length-1];
  2710.           else return null;
  2711.         ]]></getter>
  2712.       </property>
  2713.  
  2714.       <!-- the end date that should be used for getItems and similar queries -->
  2715.       <property name="queryEndDate" readonly="true">
  2716.         <getter><![CDATA[
  2717.           var end = this.endDate;
  2718.           if (!end)
  2719.               return null;
  2720.  
  2721.           end = end.clone();
  2722.           end.day += 1;
  2723.           end.isDate = true;
  2724.  
  2725.           return end;
  2726.         ]]></getter>
  2727.       </property>
  2728.  
  2729.       <property name="tasksInView">
  2730.         <getter><![CDATA[
  2731.           return this.mTasksInView;
  2732.         ]]></getter>
  2733.         <setter><![CDATA[
  2734.           this.mTasksInView = val;
  2735.           return val;
  2736.         ]]></setter>
  2737.       </property>
  2738.  
  2739.       <property name="showCompleted">
  2740.         <getter><![CDATA[
  2741.           return this.mShowCompleted;
  2742.         ]]></getter>
  2743.         <setter><![CDATA[
  2744.           this.mShowCompleted = val;
  2745.           return val;
  2746.         ]]></setter>
  2747.       </property>
  2748.  
  2749.       <property name="timezone">
  2750.         <getter><![CDATA[
  2751.           return this.mTimezone;
  2752.         ]]></getter>
  2753.         <setter><![CDATA[
  2754.           this.mTimezone = val;
  2755.           return val;
  2756.         ]]></setter>
  2757.       </property>
  2758.  
  2759.       <property name="displayDaysOff"
  2760.                 onget="return this.mDisplayDaysOff;"
  2761.                 onset="return (this.mDisplayDaysOff = val);"/>
  2762.  
  2763.       <property name="daysOffArray">
  2764.         <getter><![CDATA[
  2765.            return this.mDaysOffArray;
  2766.         ]]></getter>
  2767.         <setter><![CDATA[
  2768.            this.mDaysOffArray = val;
  2769.            return val;
  2770.         ]]></setter>
  2771.       </property>
  2772.  
  2773.      <method name="fireEvent">
  2774.        <parameter name="aEventName"/>
  2775.        <parameter name="aEventDetail"/>
  2776.        <body><![CDATA[
  2777.          var event = document.createEvent('Events');
  2778.          event.initEvent(aEventName, true, false);
  2779.          event.detail = aEventDetail;
  2780.          this.dispatchEvent(event);
  2781.        ]]></body>
  2782.      </method>
  2783.  
  2784.       <method name="showDate">
  2785.         <parameter name="aDate"/>
  2786.         <body><![CDATA[
  2787.           var targetDate = aDate.getInTimezone(this.mTimezone);
  2788.           targetDate.isDate = true;
  2789.  
  2790.           if (this.mStartDate && this.mEndDate) {
  2791.               if (this.mStartDate.compare(targetDate) <= 0 &&
  2792.                   this.mEndDate.compare(targetDate) >= 0)
  2793.                   return;
  2794.           } else if (this.mDateList) {
  2795.               for each (var d in this.mDateList) {
  2796.                   // if date is already visible, nothing to do
  2797.                   if (d.compare(targetDate) == 0)
  2798.                       return;
  2799.               }
  2800.           }
  2801.  
  2802.           // if we're only showing one date, then continue
  2803.           // to only show one date; otherwise, show the week.
  2804.           if (this.numVisibleDates == 1) {
  2805.             this.setDateRange(aDate, aDate);
  2806.           } else {
  2807.             this.setDateRange(aDate.startOfWeek, aDate.endOfWeek);
  2808.           }
  2809.  
  2810.           this.selectedDay = targetDate;
  2811.         ]]></body>
  2812.       </method>
  2813.  
  2814.       <method name="setDateRange">
  2815.         <parameter name="aStartDate"/>
  2816.         <parameter name="aEndDate"/>
  2817.         <body><![CDATA[
  2818.           // normalize dates to display timezone
  2819.           var startDate = aStartDate.getInTimezone(this.mTimezone);
  2820.           startDate.isDate = true;
  2821.           var endDate = aEndDate.getInTimezone(this.mTimezone);
  2822.           endDate.isDate = true;
  2823.           // make sure unnormalized version not used below
  2824.           aStartDate = aEndDate = null;
  2825.  
  2826.           if (this.mStartDate && this.mEndDate &&
  2827.               this.mStartDate.compare(startDate) == 0 &&
  2828.               this.mEndDate.compare(endDate) == 0) {
  2829.               // Do not change anything if the date range already matches.
  2830.               // XXX In general it should be possible to return here, but
  2831.               // lightning doesn't like it when first initializing the view.
  2832.               // return;
  2833.           }
  2834.  
  2835.           if (this.mDisplayDaysOff) {
  2836.             startDate.makeImmutable();
  2837.             endDate.makeImmutable();
  2838.             this.mDateList = null;
  2839.             this.mStartDate = startDate;
  2840.             this.mEndDate = endDate;
  2841.             //
  2842.             // For a true multiday view (e.g, 3 days advanced by one day
  2843.             // at a time), a smarter refresh could reuse boxes, comparing
  2844.             // the current date range and add/remove, instead of just
  2845.             // replacing.
  2846.             //
  2847.             this.refresh();
  2848.           } else { // workdays only
  2849.             var dateList = new Array();
  2850.             for (var d = startDate.clone(); d.compare(endDate) <= 0;) {
  2851.               if (this.mDaysOffArray.indexOf(d.weekday) == -1) {
  2852.                 var workday = d.clone();
  2853.                 workday.makeImmutable();
  2854.                 dateList.push(workday);
  2855.               }
  2856.               d.day += 1;
  2857.             }
  2858.             this.setDateList(dateList.length, dateList);
  2859.           }
  2860.         ]]></body>
  2861.       </method>
  2862.  
  2863.       <method name="setDateList">
  2864.         <parameter name="aCount"/>
  2865.         <parameter name="aDates"/>
  2866.         <body><![CDATA[
  2867.           this.mStartDate = null;
  2868.           this.mEndDate = null;
  2869.  
  2870.           if (aCount == 0) {
  2871.               this.mDateList = null;
  2872.           } else {
  2873.               aDates.sort (function(a, b) { return a.compare(b); });
  2874.               this.mDateList = aDates.map(
  2875.                   function dateMapper(d) {
  2876.                       if (d.isDate && !d.isMutable)
  2877.                           return d;
  2878.  
  2879.                       var newDate = d.clone();
  2880.                       newDate.isDate = true;
  2881.                       newDate.makeImmutable();
  2882.                       return newDate;
  2883.                   }
  2884.               );
  2885.           }
  2886.  
  2887.           this.refresh();
  2888.         ]]></body>
  2889.       </method>
  2890.  
  2891.       <method name="getDateList">
  2892.         <parameter name="aCount"/>
  2893.         <body><![CDATA[
  2894.            var dates = new Array();
  2895.            if (this.mStartDate && this.mEndDate) {
  2896.                var d = this.mStartDate.clone();
  2897.                while (d.compare(this.mEndDate) <= 0) {
  2898.                    dates.push(d.clone());
  2899.                    d.day += 1;
  2900.                }
  2901.            } else if (this.mDateList) {
  2902.                for each (var d in this.mDateList)
  2903.                    dates.push(d.clone());
  2904.            }
  2905.  
  2906.            aCount.value = dates.length;
  2907.            return dates;
  2908.         ]]></body>
  2909.       </method>
  2910.  
  2911.       <property name="selectedDay">
  2912.         <getter><![CDATA[
  2913.           if (this.numVisibleDates == 1)
  2914.             return this.mDateColumns[0].date;
  2915.  
  2916.           if (this.mSelectedDay)
  2917.             return this.mSelectedDay;
  2918.  
  2919.           if (this.mSelectedDayCol)
  2920.             return this.mSelectedDayCol.date;
  2921.  
  2922.           return null;
  2923.         ]]></getter>
  2924.         <setter><![CDATA[
  2925.           // ignore if just 1 visible, it's always selected,
  2926.           // but we don't indicate it
  2927.           if (this.numVisibleDates == 1) {
  2928.             this.fireEvent("dayselect", val);
  2929.             return val;
  2930.           }
  2931.  
  2932.           if (this.mSelectedDayCol) {
  2933.             this.mSelectedDayCol.column.selected = false;
  2934.             this.mSelectedDayCol.header.removeAttribute("selected");
  2935.           }
  2936.  
  2937.           if (val) {
  2938.             this.mSelectedDayCol = this.findColumnForDate(val);
  2939.             if (this.mSelectedDayCol) {
  2940.               this.mSelectedDay = this.mSelectedDayCol.date;
  2941.               this.mSelectedDayCol.column.selected = true;
  2942.               this.mSelectedDayCol.header.setAttribute("selected", "true");
  2943.             } else {
  2944.               this.mSelectedDay = val;
  2945.             }
  2946.           }
  2947.           this.fireEvent("dayselect", val);
  2948.           return val;
  2949.         ]]></setter>
  2950.       </property>
  2951.  
  2952.       <method name="getSelectedItems">
  2953.         <parameter name="aCount"/>
  2954.         <body><![CDATA[
  2955.           aCount.value = this.mSelectedItems.length;
  2956.           return this.mSelectedItems;
  2957.         ]]></body>
  2958.       </method>
  2959.       <method name="setSelectedItems">
  2960.         <parameter name="aCount"/>
  2961.         <parameter name="aItems"/>
  2962.         <parameter name="aSuppressEvent"/>
  2963.         <body><![CDATA[
  2964.           if (this.mSelectedItems.length) {
  2965.               for each (var item in this.mSelectedItems) {
  2966.                   var cols = this.findColumnsForItem(item);
  2967.                   for each (col in cols) {
  2968.                       col.header.unselectOccurrence(item);
  2969.                       col.column.unselectOccurrence(item);
  2970.                   }
  2971.               }
  2972.           }
  2973.           this.mSelectedItems = aItems || [];
  2974.  
  2975.           for each (var item in this.mSelectedItems) {
  2976.               var cols = this.findColumnsForItem(item);
  2977.               if (cols.length > 0) {
  2978.                   var start = item.startDate || item.entryDate;
  2979.                   for each (col in cols) {
  2980.                       if (start.isDate) {
  2981.                           col.header.selectOccurrence(item);
  2982.                       } else {
  2983.                           col.column.selectOccurrence(item);
  2984.                       }
  2985.                   }
  2986.               }
  2987.           }
  2988.  
  2989.           if (!aSuppressEvent) {
  2990.               this.fireEvent("itemselect", this.mSelectedItems);
  2991.           }
  2992.         ]]></body>
  2993.       </method>
  2994.  
  2995.       <property name="pixelsPerMinute">
  2996.         <getter>return this.mPixPerMin</getter>
  2997.         <setter>this.setPixelsPerMin(val); return val;</setter>
  2998.       </property>
  2999.  
  3000.       <property name="activeInPlaceEdit">
  3001.         <getter><![CDATA[
  3002.           return this.mInPlaceEditActive;
  3003.         ]]></getter>
  3004.         <setter><![CDATA[
  3005.           this.mInPlaceEditActive = val;
  3006.           return val;
  3007.         ]]></setter>
  3008.       </property>
  3009.  
  3010.       <!-- private -->
  3011.  
  3012.       <property name="numVisibleDates" readonly="true">
  3013.         <getter><![CDATA[
  3014.           if (this.mDateList)
  3015.             return this.mDateList.length;
  3016.  
  3017.           var count = 0;
  3018.  
  3019.           if (!this.mStartDate || !this.mEndDate) {
  3020.             // The view has not been initialized, so there are 0 visible dates.
  3021.             return count;
  3022.           }
  3023.  
  3024.           var d = this.mStartDate.clone();
  3025.           while (d.compare(this.mEndDate) <= 0) {
  3026.             count++;
  3027.             d.day += 1;
  3028.           }
  3029.  
  3030.           return count;
  3031.         ]]></getter>
  3032.       </property>
  3033.  
  3034.       <property name="orient">
  3035.         <getter><![CDATA[return (this.getAttribute("orient") || "vertical");]]></getter>
  3036.         <setter><![CDATA[this.setAttribute("orient", val); return val;]]></setter>
  3037.       </property>
  3038.  
  3039.       <method name="setAttribute">
  3040.         <parameter name="aAttr"/>
  3041.         <parameter name="aVal"/>
  3042.         <body><![CDATA[
  3043.           var needsreorient = false;
  3044.           var needsrelayout = false;
  3045.           if (aAttr == "orient") {
  3046.               if (this.getAttribute("orient") != aVal)
  3047.                   needsreorient = true;
  3048.           }
  3049.  
  3050.           if (aAttr == "context" || aAttr == "item-context")
  3051.               needsrelayout = true;
  3052.  
  3053.           // this should be done using lookupMethod(), see bug 286629
  3054.           var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
  3055.  
  3056.           if (needsrelayout && !needsreorient)
  3057.               this.relayout();
  3058.  
  3059.           if (needsreorient)
  3060.               this.reorient();
  3061.  
  3062.           return ret;
  3063.         ]]></body>
  3064.       </method>
  3065.  
  3066.       <method name="reorient">
  3067.         <body><![CDATA[
  3068.           var orient = this.getAttribute("orient");
  3069.           var otherorient = "vertical";
  3070.           if (!orient) orient = "horizontal";
  3071.           if (orient == "vertical") otherorient = "horizontal";
  3072.  
  3073.           if (orient == "horizontal")
  3074.               this.setPixelsPerMin(1.5);
  3075.           else
  3076.               this.setPixelsPerMin(0.6);
  3077.  
  3078.           var normalelems = ['mainbox', 'timebar'];
  3079.           var otherelems = ['labelbox', 'labeldaybox', 'headertimespacer',
  3080.                             'headerbox', 'headerdaybox', 'childbox', 'daybox'];
  3081.  
  3082.           for each (var id in normalelems) {
  3083.               document.getAnonymousElementByAttribute(this, "anonid", id).setAttribute("orient", orient);
  3084.           }
  3085.  
  3086.           for each (var id in otherelems) {
  3087.               document.getAnonymousElementByAttribute(this, "anonid", id).setAttribute("orient", otherorient);
  3088.           }
  3089.  
  3090.           var labelbox = document.getAnonymousElementByAttribute(this, "anonid", "labelbox");
  3091.           var labeltimespacer = document.getAnonymousElementByAttribute(this, "anonid", "labeltimespacer");
  3092.           var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox");
  3093.           var headertimespacer = document.getAnonymousElementByAttribute(this, "anonid", "headertimespacer");
  3094.           var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
  3095.           var childbox = document.getAnonymousElementByAttribute(
  3096.                          this, "anonid", "childbox");
  3097.           var mainbox = document.getAnonymousElementByAttribute(
  3098.                         this, "anonid", "mainbox");
  3099.  
  3100.           if (orient == "vertical") {
  3101.               childbox.setAttribute(
  3102.                   "style", "overflow-x: hidden; overflow-y: auto;");
  3103.               mainbox.setAttribute(
  3104.                   "style", "overflow-x: auto; overflow-y: hidden;");
  3105.           } else {
  3106.               childbox.setAttribute(
  3107.                   "style", "overflow-x: auto; overflow-y: hidden;");
  3108.               mainbox.setAttribute(
  3109.                   "style", "overflow-x: hidden; overflow-y: auto;");
  3110.           }
  3111.  
  3112.           var boxes = ["daybox", "headerdaybox"];
  3113.           for each (var boxname in boxes) {
  3114.               var box = document.getAnonymousElementByAttribute(this, "anonid", boxname);
  3115.               var child = box.firstChild;
  3116.               while (child) {
  3117.                   child.setAttribute("orient", orient);
  3118.                   child = child.nextSibling;
  3119.               }
  3120.           }
  3121.           this.refresh();
  3122.         ]]></body>
  3123.       </method>
  3124.  
  3125.       <field name="mRefreshQueue">[]</field>
  3126.       <field name="mRefreshPending">null</field>
  3127.  
  3128.       <method name="popRefreshQueue">
  3129.         <body><![CDATA[
  3130.           var pendingRefresh = this.mRefreshPending;
  3131.           if (pendingRefresh) {
  3132.               if (pendingRefresh instanceof Components.interfaces.calIOperation) {
  3133.                   this.mRefreshPending = null;
  3134.                   pendingRefresh.cancel(null);
  3135.               } else {
  3136.                   if(this.mRefreshQueue.length > 0) {
  3137.                       this.relayout();
  3138.                   }
  3139.                   return;
  3140.               }
  3141.           }
  3142.  
  3143.           var refreshJob = this.mRefreshQueue.pop();
  3144.           if (!refreshJob) {
  3145.             return;
  3146.           }
  3147.  
  3148.           if (!this.startDate || !this.endDate)
  3149.             return;
  3150.  
  3151.           // recreate our columns
  3152.           this.relayout();
  3153.  
  3154.           if (!this.mCalendar)
  3155.               return;
  3156.  
  3157.           // start our items query; for a disjoint date range
  3158.           // we get all the items, and just filter out the ones we don't
  3159.           // care about in addItem
  3160.  
  3161.           var filter = this.mCalendar.ITEM_FILTER_CLASS_OCCURRENCES;
  3162.           if (this.mShowCompleted) {
  3163.             filter |= this.mCalendar.ITEM_FILTER_COMPLETED_ALL;
  3164.           } else {
  3165.             filter |= this.mCalendar.ITEM_FILTER_COMPLETED_NO;
  3166.           }
  3167.  
  3168.           if(this.mTasksInView)
  3169.             filter |= this.mCalendar.ITEM_FILTER_TYPE_ALL;
  3170.           else
  3171.             filter |= this.mCalendar.ITEM_FILTER_TYPE_EVENT;
  3172.  
  3173.           this.mRefreshPending = true;
  3174.           pendingRefresh = this.mCalendar.getItems(filter,
  3175.                                                    0,
  3176.                                                    this.startDate,
  3177.                                                    this.queryEndDate,
  3178.                                                    this.mOperationListener);
  3179.           if (pendingRefresh && pendingRefresh.isPending) { // support for calIOperation
  3180.               this.mRefreshPending = pendingRefresh;
  3181.           }
  3182.         ]]></body>
  3183.       </method>
  3184.  
  3185.       <method name="refresh">
  3186.         <body><![CDATA[
  3187.           var refreshJob = {};
  3188.           this.mRefreshQueue.push(refreshJob);
  3189.           this.popRefreshQueue();
  3190.         ]]></body>
  3191.       </method>
  3192.  
  3193.       <method name="relayout">
  3194.         <body><![CDATA[
  3195.           function createXULElement(el) {
  3196.               return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
  3197.           }
  3198.  
  3199.           var orient = this.getAttribute("orient");
  3200.           var otherorient = "vertical";
  3201.           if (!orient) orient = "horizontal";
  3202.           if (orient == "vertical") otherorient = "horizontal";
  3203.  
  3204.           var computedDateList;
  3205.           if (this.mDateList) {
  3206.               computedDateList = this.mDateList;
  3207.           } else if (this.mStartDate && this.mEndDate) {
  3208.               computedDateList = new Array();
  3209.  
  3210.               var theDate = this.mStartDate.clone();
  3211.               while (theDate.compare(this.mEndDate) <= 0) {
  3212.                   computedDateList.push(theDate.clone());
  3213.                   theDate.day += 1;
  3214.               }
  3215.           }
  3216.  
  3217.           var daybox = document.getAnonymousElementByAttribute(this, "anonid", "daybox");
  3218.           var headerdaybox = document.getAnonymousElementByAttribute(this, "anonid", "headerdaybox");
  3219.           var labeldaybox = document.getAnonymousElementByAttribute(this, "anonid", "labeldaybox");
  3220.  
  3221.           if (!computedDateList || computedDateList.length == 0)
  3222.               return;
  3223.  
  3224.           var calView = this;
  3225.           var dayStartMin = this.mDayStartMin;
  3226.           var dayEndMin = this.mDayEndMin;
  3227.           function setUpDayEventsBox(aDayBox) {
  3228.               aDayBox.setAttribute("class", "calendar-event-column-" + (counter % 2 == 0 ? "even" : "odd"));
  3229.               aDayBox.setAttribute("context", calView.getAttribute("context"));
  3230.               aDayBox.setAttribute("item-context", calView.getAttribute("item-context") || calView.getAttribute("context"));
  3231.               aDayBox.startLayoutBatchChange();
  3232.               aDayBox.date = d;
  3233.               aDayBox.setAttribute("orient", orient);
  3234.               aDayBox.calendarView = calView;
  3235.               aDayBox.setDayStartEndMinutes(dayStartMin, dayEndMin);
  3236.           }
  3237.  
  3238.           function setUpDayHeaderBox(aDayBox) {
  3239.               aDayBox.date = d;
  3240.               aDayBox.calendarView = calView;
  3241.               aDayBox.setAttribute("orient", orient);
  3242.           }
  3243.  
  3244.           this.mDateColumns = new Array();
  3245.  
  3246.  
  3247.           // get today's date
  3248.           var today = this.today();
  3249.           var counter = 0;
  3250.           var maxDayWidth = 0;
  3251.           var dayboxkids = daybox.childNodes;
  3252.           var headerboxkids = headerdaybox.childNodes;
  3253.           var labelboxkids = labeldaybox.childNodes;
  3254.  
  3255.           for each (var d in computedDateList) {
  3256.               var dayEventsBox;
  3257.               if (counter < dayboxkids.length) {
  3258.                   dayEventsBox = dayboxkids[counter];
  3259.                   dayEventsBox.removeAttribute("relation");
  3260.                   dayEventsBox.mEvents = new Array();
  3261.               } else {
  3262.                   dayEventsBox = createXULElement("calendar-event-column");
  3263.                   dayEventsBox.setAttribute("flex", "1");
  3264.                   daybox.appendChild(dayEventsBox);
  3265.               }
  3266.               setUpDayEventsBox(dayEventsBox);
  3267.  
  3268.               var dayHeaderBox;
  3269.               if (counter < headerboxkids.length) {
  3270.                   dayHeaderBox = headerboxkids[counter];
  3271.                   dayHeaderBox.removeAttribute("today");
  3272.                   // Delete backwards to make sure we get them all
  3273.                   // and delete until no more elements are left.
  3274.                   while(dayHeaderBox.mItemBoxes.length != 0) {
  3275.                       var num = dayHeaderBox.mItemBoxes.length;
  3276.                       dayHeaderBox.deleteEvent(dayHeaderBox.mItemBoxes[num-1].occurrence);
  3277.                   }
  3278.               } else {
  3279.                   dayHeaderBox = createXULElement("calendar-header-container");
  3280.                   dayHeaderBox.setAttribute("flex", "1");
  3281.                   headerdaybox.appendChild(dayHeaderBox);
  3282.               }
  3283.               setUpDayHeaderBox(dayHeaderBox);
  3284.  
  3285.               if (0 <= this.mDaysOffArray.indexOf(d.weekday)) {
  3286.                   dayEventsBox.dayOff = true;
  3287.                   dayHeaderBox.setAttribute("weekend", "true");
  3288.               } else {
  3289.                   dayEventsBox.dayOff = false;
  3290.                   dayHeaderBox.removeAttribute("weekend");
  3291.               }
  3292.  
  3293.               // Set attributes for date relations.
  3294.               if (this.numVisibleDates > 1) {
  3295.                   switch (d.compare(today)) {
  3296.                       case -1:
  3297.                           dayHeaderBox.setAttribute("relation", "past");
  3298.                           dayEventsBox.setAttribute("relation", "past");
  3299.                           break;
  3300.                       case 0:
  3301.                           dayHeaderBox.setAttribute("relation", "today");
  3302.                           dayEventsBox.setAttribute("relation", "today");
  3303.                           break;
  3304.                       case 1:
  3305.                           dayHeaderBox.setAttribute("relation", "future");
  3306.                           dayEventsBox.setAttribute("relation", "future");
  3307.                           break;
  3308.                   }
  3309.               }
  3310.  
  3311.               var labelbox;
  3312.  
  3313.               var df = Components.classes["@mozilla.org/calendar/datetime-formatter;1"].
  3314.                        getService(Components.interfaces.calIDateTimeFormatter);
  3315.               if (counter < labelboxkids.length) {
  3316.                   labelbox = labelboxkids[counter];
  3317.                   labelbox.date = d;
  3318.                   labelbox.shortWeekNames = false;
  3319.               } else {
  3320.                   labelbox = createXULElement("calendar-day-label");
  3321.                   labelbox.setAttribute("flex", "1");
  3322.                   labeldaybox.appendChild(labelbox);
  3323.  
  3324.                   labelbox.date = d;
  3325.                   labelbox.shortWeekNames = false;
  3326.               }
  3327.               maxDayWidth = Math.max(maxDayWidth, labelbox.longWeekdayPixels);
  3328.  
  3329.               // We don't want to actually mess with our original dates, plus
  3330.               // they're likely to be immutable.
  3331.               var d2 = d.clone();
  3332.               d2.isDate = true;
  3333.               d2.makeImmutable();
  3334.               this.mDateColumns.push ( { date: d2, column: dayEventsBox, header: dayHeaderBox } );
  3335.               counter++;
  3336.           }
  3337.  
  3338.           // Remove any extra columns that may have been hanging around
  3339.           function removeExtraKids(elem) {
  3340.               while (counter < elem.childNodes.length) {
  3341.                   elem.removeChild(elem.childNodes[counter]);
  3342.               }
  3343.           }
  3344.           removeExtraKids(daybox);
  3345.           removeExtraKids(headerdaybox);
  3346.           removeExtraKids(labeldaybox);
  3347.  
  3348.           // Cache weekday length, give a 10 px grace period.
  3349.           this.mLongWeekdayTotalPixels = maxDayWidth * labeldaybox.childNodes.length + 10;
  3350.  
  3351.           // fix pixels-per-minute
  3352.           this.onResize();
  3353.           for each (col in this.mDateColumns) {
  3354.                col.column.endLayoutBatchChange();
  3355.           }
  3356.  
  3357.           // adjust scrollbar spacers
  3358.           // XXX For performance reasons this method call can be moved to the
  3359.           // XXX constructor and the reorient method as soon as the views
  3360.           // XXX are constructed statically (24 hrs).
  3361.           this.adjustScrollBarSpacers();
  3362.         ]]></body>
  3363.       </method>
  3364.  
  3365.       <method name="findColumnForDate">
  3366.         <parameter name="aDate"/>
  3367.         <body><![CDATA[
  3368.           for each (var col in this.mDateColumns) {
  3369.               if (col.date.compare(aDate) == 0)
  3370.                   return col;
  3371.           }
  3372.           return null;
  3373.         ]]></body>
  3374.       </method>
  3375.  
  3376.       <method name="findColumnsForItem">
  3377.         <parameter name="aItem"/>
  3378.         <body><![CDATA[
  3379.           var columns = new Array();
  3380.  
  3381.           if (!this.mDateColumns) {
  3382.             return columns;
  3383.           }
  3384.  
  3385.           var tz = this.mDateColumns[0].date.timezone;
  3386.  
  3387.           // Note that these may be dates or datetimes
  3388.           var startDate = aItem.startDate || aItem.entryDate;
  3389.           if (!startDate) {
  3390.               return columns;
  3391.           }
  3392.           var targetDate = startDate.getInTimezone(tz);
  3393.           var endDate = aItem.endDate || aItem.dueDate || startDate;
  3394.           var finishDate = endDate.getInTimezone(tz);
  3395.  
  3396.           if (!targetDate.isDate) {
  3397.             // Set the time to 00:00 so that we get all the boxes
  3398.             targetDate.hour = 0;
  3399.             targetDate.minute = 0;
  3400.             targetDate.second = 0;
  3401.           }
  3402.  
  3403.           if (targetDate.compare(finishDate) == 0) {
  3404.               // Zero length events are silly, but we have to handle them
  3405.               var col = this.findColumnForDate(targetDate);
  3406.               if (col) {
  3407.                   columns.push(col);
  3408.               }
  3409.           }
  3410.  
  3411.           while (targetDate.compare(finishDate) == -1) {
  3412.             var col = this.findColumnForDate(targetDate);
  3413.  
  3414.             // This might not exist if the event spans the view start or end
  3415.             if (col) {
  3416.                 columns.push(col);
  3417.             }
  3418.             targetDate.day += 1;
  3419.           }
  3420.  
  3421.           return columns;
  3422.         ]]></body>
  3423.       </method>
  3424.  
  3425.       <!-- for the given client-coord-system point, return
  3426.          - the calendar-event-column that contains it.  If
  3427.          - no column contains it, return null.
  3428.         -->
  3429.       <method name="findColumnForClientPoint">
  3430.         <parameter name="aClientX"/>
  3431.         <parameter name="aClientY"/>
  3432.         <body><![CDATA[
  3433.           for each (var col in this.mDateColumns) {
  3434.               var bo = col.column.topbox.boxObject;
  3435.               if ((aClientX >= bo.screenX) && (aClientX < (bo.screenX + bo.width)) &&
  3436.                   (aClientY >= bo.screenY) && (aClientY < (bo.screenY + bo.height)))
  3437.               {
  3438.                   return col.column;
  3439.               }
  3440.           }
  3441.           return null;
  3442.         ]]></body>
  3443.       </method>
  3444.  
  3445.       <method name="doAddEvent">
  3446.         <parameter name="aEvent"/>
  3447.         <body><![CDATA[
  3448.           //dump ("++ doAddevent\n");
  3449.           var cols = this.findColumnsForItem(aEvent);
  3450.           if (!cols.length)
  3451.               return;
  3452.  
  3453.           for each (col in cols) {
  3454.               var column = col.column;
  3455.               var header = col.header;
  3456.  
  3457.               var estart = aEvent.startDate || aEvent.entryDate;
  3458.               if (estart.isDate) {
  3459.                   header.addEvent(aEvent);
  3460.               } else {
  3461.                   column.addEvent(aEvent);
  3462.               }
  3463.           }
  3464.         ]]></body>
  3465.       </method>
  3466.  
  3467.       <method name="doDeleteEvent">
  3468.         <parameter name="aEvent"/>
  3469.         <body><![CDATA[
  3470.           var cols = this.findColumnsForItem(aEvent);
  3471.           if (!cols.length)
  3472.               return;
  3473.  
  3474.           for each (col in cols) {
  3475.               var column = col.column;
  3476.               var header = col.header;
  3477.  
  3478.               var estart = aEvent.startDate || aEvent.entryDate;
  3479.               if (estart.isDate) {
  3480.                   header.deleteEvent(aEvent);
  3481.               } else {
  3482.                   column.deleteEvent(aEvent);
  3483.               }
  3484.           }
  3485.  
  3486.           // See whether the item we are deleting was selected.  If it was, then
  3487.           // fire the appropriate event so our watchers can update
  3488.           var found = false;
  3489.           for (var i = 0; i < this.mSelectedItems.length; i++) {
  3490.               if (this.mSelectedItems[i].hashId == aEvent.hashId) {
  3491.                   this.mSelectedItems.splice(i, 1);
  3492.                   found = true;
  3493.               }
  3494.           }
  3495.           if (found) {
  3496.               this.fireEvent("itemselect", this.mSelectedItems);
  3497.           }
  3498.         ]]></body>
  3499.       </method>
  3500.  
  3501.       <method name="setPixelsPerMin">
  3502.         <parameter name="pixPerMin"/>
  3503.         <body><![CDATA[
  3504.           this.mPixPerMin = pixPerMin;
  3505.  
  3506.           var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
  3507.           timebar.pixelsPerMinute = pixPerMin;
  3508.  
  3509.           for each (var col in this.mDateColumns) {
  3510.               col.column.pixelsPerMinute = pixPerMin;
  3511.           }
  3512.         ]]></body>
  3513.       </method>
  3514.  
  3515.       <method name="today">
  3516.         <body><![CDATA[
  3517.           var date = createDateTime();
  3518.           date.jsDate = new Date();
  3519.           date = date.getInTimezone(this.mTimezone);
  3520.           date.isDate = true;
  3521.           return date;
  3522.         ]]></body>
  3523.       </method>
  3524.  
  3525.       <method name="adjustWeekdayLength">
  3526.         <body><![CDATA[
  3527.           // This function is called from the resize handler and also from the
  3528.           // overflow event of the mainbox. The latter guarantees that the
  3529.           // labels are clipped in the instance that the overflow occurrs,
  3530.           // avoiding horizontal scrollbars from showing up for a short period.
  3531.  
  3532.           var labeldaybox = document.getAnonymousElementByAttribute(this, "anonid", "labeldaybox");
  3533.           var labeldayboxkids = labeldaybox.childNodes;
  3534.           if (!labeldayboxkids || !labeldayboxkids[0]) {
  3535.             // This happens in rotated view. Don't do anything there.
  3536.             return;
  3537.           }
  3538.  
  3539.           if (labeldayboxkids[0].boxObject.width * this.daysInView > this.mLongWeekdayTotalPixels) {
  3540.             // We have an underflow. Since the boxes all have equal width, we
  3541.             // can use the first boxes' width and multiply bu the number of
  3542.             // days in the view.
  3543.             for (var i = 0; i < labeldayboxkids.length; i++) {
  3544.               labeldayboxkids[i].shortWeekNames = false;
  3545.             }
  3546.           } else {
  3547.             // We have an overflow
  3548.             for (var i = 0; i < labeldayboxkids.length; i++) {
  3549.               labeldayboxkids[i].shortWeekNames = true;
  3550.             }
  3551.           }
  3552.         ]]></body>
  3553.       </method>
  3554.  
  3555.       <method name="adjustScrollBarSpacers">
  3556.         <body><![CDATA[
  3557.           // get the view's orientation
  3558.           var propertyName;
  3559.           if (this.getAttribute("orient") == "vertical") {
  3560.               propertyName = "width";
  3561.           } else {
  3562.               propertyName = "height";
  3563.           }
  3564.  
  3565.           // get the width/height of the childbox scrollbar
  3566.           var childbox = document.getAnonymousElementByAttribute(
  3567.                          this, "anonid", "childbox");
  3568.           var propertyValue = childbox.boxObject.firstChild
  3569.                               .boxObject[propertyName];
  3570.  
  3571.           // set the same width/height for the label and header box spacers
  3572.           var headerScrollBarSpacer = document.getAnonymousElementByAttribute(
  3573.                                       this, "anonid", "headerscrollbarspacer");
  3574.           headerScrollBarSpacer.setAttribute(propertyName, propertyValue);
  3575.           var labelScrollBarSpacer = document.getAnonymousElementByAttribute(
  3576.                                      this, "anonid", "labelscrollbarspacer");
  3577.           labelScrollBarSpacer.setAttribute(propertyName, propertyValue);
  3578.         ]]></body>
  3579.       </method>
  3580.  
  3581.       <field name="mFirstVisibleMinute">0</field>
  3582.       <method name="getFirstVisibleMinute">
  3583.         <body><![CDATA[
  3584.           var minute = 0;
  3585.           var childbox = document.getAnonymousElementByAttribute(this, "anonid", "childbox");
  3586.           var scrollBoxObject = childbox.boxObject.QueryInterface(Components.interfaces.nsIScrollBoxObject);
  3587.           if (scrollBoxObject != null) {
  3588.             var x = {};
  3589.             var y = {};
  3590.             scrollBoxObject.getPosition(x, y);
  3591.             if (childbox.getAttribute("orient") == "horizontal") {
  3592.               minute = Math.round(y.value/this.mPixPerMin);
  3593.             } else {
  3594.               minute = Math.round(x.value/this.mPixPerMin);
  3595.             }
  3596.           }
  3597.           return minute;
  3598.         ]]></body>
  3599.       </method>
  3600.  
  3601.       <method name="setFirstVisibleMinute">
  3602.         <parameter name="aMinute"/>
  3603.         <body><![CDATA[
  3604.           this.mFirstVisibleMinute = aMinute;
  3605.           return this.mFirstVisibleMinute;
  3606.         ]]></body>
  3607.       </method>
  3608.  
  3609.       <method name="scrollToMinute">
  3610.         <parameter name="aMinute"/>
  3611.         <body><![CDATA[
  3612.           var childbox = document.getAnonymousElementByAttribute(this, "anonid", "childbox");
  3613.           var scrollBoxObject = childbox.boxObject.QueryInterface(Components.interfaces.nsIScrollBoxObject);
  3614.           if (scrollBoxObject != null) {
  3615.             var x = {};
  3616.             var y = {};
  3617.             scrollBoxObject.getPosition(x, y);
  3618.             var pos = Math.round(aMinute * this.mPixPerMin);
  3619.             if (childbox.getAttribute("orient") == "horizontal") {
  3620.               scrollBoxObject.scrollTo(x.value, pos);
  3621.             } else {
  3622.               scrollBoxObject.scrollTo(pos, y.value);
  3623.             }
  3624.           }
  3625.         ]]></body>
  3626.       </method>
  3627.  
  3628.       <method name="setDayStartEndMinutes">
  3629.         <parameter name="aDayStartMin"/>
  3630.         <parameter name="aDayEndMin"/>
  3631.         <body><![CDATA[
  3632.           if (aDayStartMin < this.mStartMin || aDayStartMin > aDayEndMin ||
  3633.             aDayEndMin > this.mEndMin) {
  3634.             throw Components.results.NS_ERROR_INVALID_ARG;
  3635.           }
  3636.           if (this.mDayStartMin != aDayStartMin ||
  3637.             this.mDayEndMin != aDayEndMin) {
  3638.  
  3639.             this.mDayStartMin = aDayStartMin;
  3640.             this.mDayEndMin = aDayEndMin;
  3641.  
  3642.             // Also update on the time-bar
  3643.             document.getAnonymousElementByAttribute(this, "anonid", "timebar")
  3644.                     .setDayStartEndHours(this.mDayStartMin / 60,
  3645.                                          this.mDayEndMin / 60);
  3646.           }
  3647.  
  3648.         ]]></body>
  3649.       </method>
  3650.  
  3651.       <method name="setVisibleMinutes">
  3652.         <parameter name="aVisibleMinutes"/>
  3653.         <body><![CDATA[
  3654.           if (aVisibleMinutes <= 0 ||
  3655.             aVisibleMinutes > (this.mEndMin - this.mStartMin)) {
  3656.             throw Components.results.NS_ERROR_INVALID_ARG;
  3657.           }
  3658.           if (this.mVisibleMinutes != aVisibleMinutes) {
  3659.             this.mVisibleMinutes = aVisibleMinutes;
  3660.           }
  3661.           return this.mVisibleMinutes;
  3662.         ]]></body>
  3663.       </method>
  3664.     </implementation>
  3665.  
  3666.     <handlers>
  3667.       <handler event="DOMMouseScroll"><![CDATA[
  3668.         if (!event.ctrlKey &&
  3669.             !event.shiftKey &&
  3670.             !event.altKey &&
  3671.             !event.metaKey) {
  3672.             // Only shift hours if no modifier is pressed.
  3673.             this.scrollToMinute(this.mFirstVisibleMinute +
  3674.                                 (event.detail < 0 ? -60 : 60));
  3675.         }
  3676.  
  3677.         // We are taking care of scrolling, so prevent the default
  3678.         // action in any case.
  3679.         event.preventDefault();
  3680.       ]]></handler>
  3681.       <handler event="keypress"><![CDATA[
  3682.         const kKE = Components.interfaces.nsIDOMKeyEvent;
  3683.         if (event.keyCode == kKE.DOM_VK_BACK_SPACE ||
  3684.             event.keyCode == kKE.DOM_VK_DELETE)
  3685.         {
  3686.             if (!this.activeInPlaceEdit && this.mSelectedItems.length && this.controller) {
  3687.                 this.controller.deleteOccurrences(this.mSelectedItems.length,
  3688.                                                   this.mSelectedItems,
  3689.                                                   event.ctrlKey,
  3690.                                                   false);
  3691.             }
  3692.         }
  3693.       ]]></handler>
  3694.     </handlers>
  3695.   </binding>
  3696. </bindings>
  3697.  
  3698. <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
  3699.